gnunet-svn
[Top][All Lists]
Advanced

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

[gnunet-go] branch master updated: Refactored GNS zone crypto implementa


From: gnunet
Subject: [gnunet-go] branch master updated: Refactored GNS zone crypto implementation.
Date: Wed, 02 Feb 2022 15:17:31 +0100

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 205cad6  Refactored GNS zone crypto implementation.
205cad6 is described below

commit 205cad60026bf0af1cd2712a8faa4bce08eafb1d
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Wed Feb 2 15:14:12 2022 +0100

    Refactored GNS zone crypto implementation.
---
 build.sh                                     |   3 +-
 src/cmd/gnunet-service-gns-go/main.go        |  31 +-
 src/cmd/gnunet-service-revocation-go/main.go |  29 +-
 src/gnunet/config/config.go                  |  24 +-
 src/gnunet/config/gnunet-config.json         |   3 +
 src/gnunet/crypto/gns.go                     | 432 +++++++++++++++++++++++++--
 src/gnunet/crypto/gns_edkey.go               | 245 +++++++++++++++
 src/gnunet/crypto/gns_pkey.go                | 241 +++++++++++++++
 src/gnunet/crypto/gns_test.go                | 370 ++++++++++++++++++-----
 src/gnunet/crypto/key_derivation.go          |  50 ----
 src/gnunet/crypto/key_derivation_test.go     | 228 --------------
 src/gnunet/crypto/key_exchange_test.go       |   7 +-
 src/gnunet/crypto/symmetric.go               | 108 -------
 src/gnunet/enums/gns.go                      |   1 +
 src/gnunet/go.mod                            |  18 +-
 src/gnunet/go.sum                            | 119 +++++++-
 src/gnunet/message/const.go                  |  12 +-
 src/gnunet/message/factory.go                |   7 +-
 src/gnunet/message/message.go                |  14 +-
 src/gnunet/message/msg_core.go               |   8 +-
 src/gnunet/message/msg_dht.go                |  38 +--
 src/gnunet/message/msg_gns.go                | 193 ++++++------
 src/gnunet/message/msg_gns_test.go           | 353 ++++++++++++++++++++++
 src/gnunet/message/msg_namecache.go          |  99 +++---
 src/gnunet/message/msg_revocation.go         |  86 +++---
 src/gnunet/message/msg_transport.go          |  76 ++---
 src/gnunet/modules.go                        |  23 +-
 src/gnunet/rpc/server.go                     |  66 ++++
 src/gnunet/service/client.go                 |   4 +-
 src/gnunet/service/context.go                |   4 +-
 src/gnunet/service/dht/module.go             |  10 +-
 src/gnunet/service/gns/block_handler.go      | 160 +++++-----
 src/gnunet/service/gns/box.go                |   6 +-
 src/gnunet/service/gns/dns.go                |  39 +--
 src/gnunet/service/gns/module.go             | 160 +++++-----
 src/gnunet/service/gns/service.go            | 139 ++++-----
 src/gnunet/service/namecache/module.go       |   4 +-
 src/gnunet/service/revocation/module.go      |  39 ++-
 src/gnunet/service/revocation/pow.go         |  71 ++---
 src/gnunet/service/revocation/pow_test.go    | 235 ++++++++-------
 src/gnunet/service/revocation/service.go     |  63 ++--
 src/gnunet/service/service.go                |  39 ++-
 src/gnunet/transport/channel.go              |  14 +-
 src/gnunet/transport/channel_netw.go         |  20 +-
 src/gnunet/transport/channel_test.go         |  87 +++---
 src/gnunet/transport/connection.go           |   2 -
 src/gnunet/transport/session.go              |  12 +-
 src/gnunet/util/address.go                   |   2 +-
 src/gnunet/util/array.go                     |   8 +-
 src/gnunet/util/database.go                  |  20 +-
 src/gnunet/util/id.go                        |   3 +-
 src/gnunet/util/key_value_store.go           |  22 +-
 src/gnunet/util/misc.go                      |   6 +-
 src/gnunet/util/time.go                      |   2 +-
 src/gnunet/util/{id.go => time_test.go}      |  29 +-
 test.sh                                      |   3 +-
 56 files changed, 2730 insertions(+), 1357 deletions(-)

diff --git a/build.sh b/build.sh
index 660ded9..b7716a0 100755
--- a/build.sh
+++ b/build.sh
@@ -1,3 +1,4 @@
 #!/bin/bash
 
-GOPATH=$(pwd):${GOPATH} go install -v -gcflags "-N -l" ./...
+cd src/gnunet/
+go install -v -gcflags "-N -l" ./...
diff --git a/src/cmd/gnunet-service-gns-go/main.go 
b/src/cmd/gnunet-service-gns-go/main.go
index 2815eaa..57eec7e 100644
--- a/src/cmd/gnunet-service-gns-go/main.go
+++ b/src/cmd/gnunet-service-gns-go/main.go
@@ -19,16 +19,20 @@
 package main
 
 import (
+       "context"
        "flag"
        "os"
        "os/signal"
+       "strings"
        "syscall"
        "time"
 
-       "github.com/bfix/gospel/logger"
        "gnunet/config"
+       "gnunet/rpc"
        "gnunet/service"
        "gnunet/service/gns"
+
+       "github.com/bfix/gospel/logger"
 )
 
 func main() {
@@ -44,11 +48,13 @@ func main() {
                srvEndp  string
                err      error
                logLevel int
+               rpcEndp  string
        )
        // handle command line arguments
        flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet 
configuration file")
        flag.StringVar(&srvEndp, "s", "", "GNS service end-point")
        flag.IntVar(&logLevel, "L", logger.INFO, "GNS log level (default: 
INFO)")
+       flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
        flag.Parse()
 
        // read configuration file and set missing arguments.
@@ -64,13 +70,31 @@ func main() {
        }
 
        // start a new GNS service
-       gns := gns.NewGNSService()
+       gns := gns.NewService()
        srv := service.NewServiceImpl("gns", gns)
        if err = srv.Start(srvEndp); err != nil {
-               logger.Printf(logger.ERROR, "[gns] Error: '%s'\n", err.Error())
+               logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
                return
        }
 
+       // start JSON-RPC server on request
+       var cancel func() = func() {}
+       if len(rpcEndp) > 0 {
+               var ctx context.Context
+               ctx, cancel = context.WithCancel(context.Background())
+               parts := strings.Split(rpcEndp, "+")
+               if parts[0] != "tcp" {
+                       logger.Println(logger.ERROR, "[gns] RPC must have a 
TCP/IP endpoint")
+                       return
+               }
+               config.Cfg.RPC.Endpoint = parts[1]
+               if err = rpc.Start(ctx); err != nil {
+                       logger.Printf(logger.ERROR, "[gns] RPC failed to start: 
%s", err.Error())
+                       return
+               }
+               rpc.Register(gns)
+       }
+
        // handle OS signals
        sigCh := make(chan os.Signal, 5)
        signal.Notify(sigCh)
@@ -101,5 +125,6 @@ loop:
        }
 
        // terminating service
+       cancel()
        srv.Stop()
 }
diff --git a/src/cmd/gnunet-service-revocation-go/main.go 
b/src/cmd/gnunet-service-revocation-go/main.go
index c829aea..ec5ce73 100644
--- a/src/cmd/gnunet-service-revocation-go/main.go
+++ b/src/cmd/gnunet-service-revocation-go/main.go
@@ -19,16 +19,20 @@
 package main
 
 import (
+       "context"
        "flag"
        "os"
        "os/signal"
+       "strings"
        "syscall"
        "time"
 
-       "github.com/bfix/gospel/logger"
        "gnunet/config"
+       "gnunet/rpc"
        "gnunet/service"
        "gnunet/service/revocation"
+
+       "github.com/bfix/gospel/logger"
 )
 
 func main() {
@@ -44,11 +48,13 @@ func main() {
                srvEndp  string
                err      error
                logLevel int
+               rpcEndp  string
        )
        // handle command line arguments
        flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet 
configuration file")
        flag.StringVar(&srvEndp, "s", "", "REVOCATION service end-point")
        flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level 
(default: INFO)")
+       flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
        flag.Parse()
 
        // read configuration file and set missing arguments.
@@ -64,13 +70,31 @@ func main() {
        }
 
        // start a new REVOCATION service
-       rvc := revocation.NewRevocationService()
+       rvc := revocation.NewService()
        srv := service.NewServiceImpl("revocation", rvc)
        if err = srv.Start(srvEndp); err != nil {
                logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n", 
err.Error())
                return
        }
 
+       // start JSON-RPC server on request
+       var cancel func() = func() {}
+       if len(rpcEndp) > 0 {
+               var ctx context.Context
+               ctx, cancel = context.WithCancel(context.Background())
+               parts := strings.Split(rpcEndp, "+")
+               if parts[0] != "tcp" {
+                       logger.Println(logger.ERROR, "[revocation] RPC must 
have a TCP/IP endpoint")
+                       return
+               }
+               config.Cfg.RPC.Endpoint = parts[1]
+               if err = rpc.Start(ctx); err != nil {
+                       logger.Printf(logger.ERROR, "[revocation] RPC failed to 
start: %s", err.Error())
+                       return
+               }
+               rpc.Register(rvc)
+       }
+
        // handle OS signals
        sigCh := make(chan os.Signal, 5)
        signal.Notify(sigCh)
@@ -101,5 +125,6 @@ loop:
        }
 
        // terminating service
+       cancel()
        srv.Stop()
 }
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 690b186..914a017 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -28,10 +28,18 @@ import (
        "github.com/bfix/gospel/logger"
 )
 
+///////////////////////////////////////////////////////////////////////
+// RPC configuration
+
+// RPCConfig contains parameters for the JSON-RPC service
+type RPCConfig struct {
+       Endpoint string `json:"endpoint"` // end-point of JSON-RPC service
+}
+
 ///////////////////////////////////////////////////////////////////////
 // GNS configuration
 
-// GNSConfig
+// GNSConfig contains parameters for the GNU Name System service
 type GNSConfig struct {
        Endpoint     string `json:"endpoint"`     // end-point of GNS service
        DHTReplLevel int    `json:"dhtReplLevel"` // DHT replication level
@@ -41,7 +49,7 @@ type GNSConfig struct {
 ///////////////////////////////////////////////////////////////////////
 // DHT configuration
 
-// DHTConfig
+// DHTConfig contains parameters for the distributed hash table (DHT)
 type DHTConfig struct {
        Endpoint string `json:"endpoint"` // end-point of DHT service
 }
@@ -49,7 +57,7 @@ type DHTConfig struct {
 ///////////////////////////////////////////////////////////////////////
 // Namecache configuration
 
-// NamecacheConfig
+// NamecacheConfig contains parameters for the local name cache
 type NamecacheConfig struct {
        Endpoint string `json:"endpoint"` // end-point of Namecache service
 }
@@ -57,7 +65,7 @@ type NamecacheConfig struct {
 ///////////////////////////////////////////////////////////////////////
 // Revocation configuration
 
-// RevocationConfig
+// RevocationConfig contains parameters for the key revocation service
 type RevocationConfig struct {
        Endpoint string `json:"endpoint"` // end-point of Revocation service
        Storage  string `json:"storage"`  // persistance mechanism for 
revocation data
@@ -66,11 +74,12 @@ type RevocationConfig struct {
 ///////////////////////////////////////////////////////////////////////
 
 // Environment settings
-type Environ map[string]string
+type Environment map[string]string
 
 // Config is the aggregated configuration for GNUnet.
 type Config struct {
-       Env        Environ           `json:"environ"`
+       Env        Environment       `json:"environ"`
+       RPC        *RPCConfig        `json:"rpc"`
        DHT        *DHTConfig        `json:"dht"`
        GNS        *GNSConfig        `json:"gns"`
        Namecache  *NamecacheConfig  `json:"namecache"`
@@ -82,7 +91,8 @@ var (
        Cfg *Config
 )
 
-// Parse a JSON-encoded configuration file map it to the Config data structure.
+// ParseConfig converts a JSON-encoded configuration file and maps it to
+// the Config data structure.
 func ParseConfig(fileName string) (err error) {
        // parse configuration file
        file, err := ioutil.ReadFile(fileName)
diff --git a/src/gnunet/config/gnunet-config.json 
b/src/gnunet/config/gnunet-config.json
index aefc9ee..daf65f9 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -17,5 +17,8 @@
        "revocation": {
                "endpoint": 
"unix+${RT_SYS}/gnunet-service-revocation-go.sock+perm=0770",
                "storage": "redis+localhost:6397++15"
+       },
+       "rpc": {
+               "endpoint": "tcp+127.0.0.1:80"
        }
 }
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index 8b33057..6aa7972 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -1,5 +1,5 @@
 // This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+// 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
@@ -19,35 +19,415 @@
 package crypto
 
 import (
+       "bytes"
        "crypto/sha256"
        "crypto/sha512"
+       "encoding/binary"
+       "errors"
+       "gnunet/enums"
+       "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/math"
        "golang.org/x/crypto/hkdf"
 )
 
-// DeriveBlockKey returns a symmetric key and initialization vector to 
decipher a GNS block.
-func DeriveBlockKey(label string, pub *ed25519.PublicKey) (iv *SymmetricIV, 
skey *SymmetricKey) {
-       // generate symmetric key
-       prk := hkdf.Extract(sha512.New, pub.Bytes(), []byte("gns-aes-ctx-key"))
-       rdr := hkdf.Expand(sha256.New, prk, []byte(label))
-       skey = NewSymmetricKey()
-       rdr.Read(skey.AESKey)
-       rdr.Read(skey.TwofishKey)
-
-       // generate initialization vector
-       prk = hkdf.Extract(sha512.New, pub.Bytes(), []byte("gns-aes-ctx-iv"))
-       rdr = hkdf.Expand(sha256.New, prk, []byte(label))
-       iv = NewSymmetricIV()
-       rdr.Read(iv.AESIv)
-       rdr.Read(iv.TwofishIv)
-       return
-}
-
-// DecryptBlock for a given zone and label.
-func DecryptBlock(data []byte, zoneKey *ed25519.PublicKey, label string) (out 
[]byte, err error) {
-       // derive key material for decryption
-       iv, skey := DeriveBlockKey(label, zoneKey)
-       // perform decryption
-       return SymmetricDecrypt(data, skey, iv)
+//======================================================================
+// All zone-related cryptography in GNS is encapsulated in three
+// distinct types: ZonePrivate, ZoneKey (public) and ZoneSignature.
+// To enable crypto-agility, these types are implemented in a generic
+// way - mostly as byte arrays holding the specific representation of a
+// crypto implementation.
+//
+// Currently two key systems are implemented:
+//   * PKEY: Ed25519 keys with private scalar and ECDSA signatures
+//   * EDKEY: Ed25519 keys for EdDSA signatures (Ed25519 standard)
+//
+// It is easy to implement new crypto schemes as long as the following
+// criteria are met:
+//   * It is an asymmetric crypto scheme (with private and public key)
+//   * It can encrypt data with a public key and decrypt it with the
+//     corresponding private key. How that is done is completely up to
+//     the specific implementation.
+//   * It can sign data with the private key and verify it with the
+//     corresponding public key.
+//   * It can do key blinding (public and private) based on a 64 byte
+//     byte array. How that is done is up to the specific implementation.
+//
+// The way to add new zone crypto implementation is as follows; as an
+// example the RSA crypto scheme is outlined:
+//
+//   (1) Register/define a new GNS_TYPE_RSAKEY
+//   (2) Add ZONE_RSAKEY to the "Zone types" declarations below.
+//   (3) Code the implementation in a file named `gns_rsakey.go`:
+//       You have to implement three interfaces (ZonePrivateImpl,
+//       ZoneKeyImpl and ZoneSigImpl) in three separate custom types.
+//       Additionally an instantiation function (zero value) must be
+//       defined for all three custom types (like 'NewRSAPrivate()'
+//       taking no arguments and returning an empty new instance.
+//   (4) In the 'init()' method of your source file, register the
+//       implementation in the "Zone implementations" below with:
+//           zoneImpl[ZONE_RSAKEY] = &ZoneImplementation{
+//               NewPrivate: NewRSAPrivate,
+//               PrivateSize: 256,
+//               NewPublic: NewRSAPublic,
+//               PublicSize: 270.
+//               NewSignature: newRSASignature,
+//               SignatureSize: 512,
+//           }
+//   Review a provided implementation (like `gns_edkey.go`) as an
+//   example on how to create a custom GNS zone crypto.
+//   (5) Add the zone type to the GNS block handler in file
+//       `service/gns/block_handler.go`:
+//           ;
+//           enums.GNS_TYPE_RSAKEY:     NewZoneHandler,
+//           ;
+//
+//======================================================================
+
+//----------------------------------------------------------------------
+// Implementation interfaces
+//----------------------------------------------------------------------
+
+// ZoneAbstractImpl is an abstract interface used in derived interfaces
+type ZoneAbstractImpl interface {
+       // Init the instance from given binary representation
+       Init(data []byte) error
+
+       // Bytes returns the binary representation (can be used with 'init()')
+       Bytes() []byte
+}
+
+// ZoneKeyImpl defines the methods for a public zone key.
+type ZoneKeyImpl interface {
+       ZoneAbstractImpl
+
+       // 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)
+
+       // BlockKey returns the key for block en-/decryption
+       BlockKey(label string, expires util.AbsoluteTime) (skey []byte)
+
+       // Encrypt binary data (of any size). Output can be larger than input
+       Encrypt(data []byte, label string, expires util.AbsoluteTime) ([]byte, 
error)
+
+       // Decrypt data (of any size). Output can be smaller than input
+       Decrypt(data []byte, label string, expires util.AbsoluteTime) ([]byte, 
error)
+
+       // Verify a signature for binary data
+       Verify(data []byte, sig *ZoneSignature) (bool, error)
+}
+
+// ZonePrivateImpl defines the methods for a private zone key.
+type ZonePrivateImpl interface {
+       ZoneAbstractImpl
+
+       // 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)
+
+       // Sign binary data and return the signature
+       Sign(data []byte) (*ZoneSignature, error)
+
+       // Public returns the associated public key
+       Public() ZoneKeyImpl
+}
+
+// ZoneSigImpl defines the methods for a signature object.
+type ZoneSigImpl interface {
+       ZoneAbstractImpl
+}
+
+//----------------------------------------------------------------------
+// Zone types
+//----------------------------------------------------------------------
+var (
+       ZONE_PKEY  = uint32(enums.GNS_TYPE_PKEY)
+       ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
+)
+
+//----------------------------------------------------------------------
+// Zone implementations
+//----------------------------------------------------------------------
+
+// ZoneImplementation holds factory methods and size values for a
+// specific crypto implementation (based on the associated zone type)
+type ZoneImplementation struct {
+       NewPrivate    func() ZonePrivateImpl
+       PrivateSize   uint
+       NewPublic     func() ZoneKeyImpl
+       PublicSize    uint
+       NewSignature  func() ZoneSigImpl
+       SignatureSize uint
+}
+
+// keep a mapping of available implementations
+var (
+       zoneImpl = make(map[uint32]*ZoneImplementation)
+)
+
+// Error codes
+var (
+       ErrNoImplementation = errors.New("unknown zone implementation")
+)
+
+// GetImplementation return the factory for a given zone type.
+// If zje zone type is unregistered, nil is returned.
+func GetImplementation(ztype uint32) *ZoneImplementation {
+       if impl, ok := zoneImpl[ztype]; ok {
+               return impl
+       }
+       return nil
+}
+
+//======================================================================
+// Generic implementations:
+//======================================================================
+
+//----------------------------------------------------------------------
+// Zone key (private)
+//----------------------------------------------------------------------
+
+// ZonePrivate represents the possible types of private zone keys (PKEY, 
EDKEY,...)
+type ZonePrivate struct {
+       ZoneKey
+
+       impl ZonePrivateImpl // reference to implementation
+}
+
+// NewZonePrivate returns a new initialized ZonePrivate instance
+func NewZonePrivate(ztype uint32, d []byte) (*ZonePrivate, error) {
+       // get factory for given zone type
+       impl, ok := zoneImpl[ztype]
+       if !ok {
+               return nil, ErrNoImplementation
+       }
+       // assemble private zone key
+       zp := &ZonePrivate{
+               ZoneKey{
+                       ztype,
+                       nil,
+                       nil,
+               },
+               nil,
+       }
+       zp.impl = impl.NewPrivate()
+       zp.impl.Init(d)
+       zp.ZoneKey.KeyData = zp.impl.Public().Bytes()
+       zp.ZoneKey.impl = impl.NewPublic()
+       zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+       return zp, nil
+}
+
+// KeySize returns the number of bytes of a key representation.
+// This method is used during serialization (Unmarshal).
+func (zp *ZonePrivate) KeySize() uint {
+       if impl, ok := zoneImpl[zp.Type]; ok {
+               return impl.PrivateSize
+       }
+       return 0
+}
+
+// Derive key (key blinding)
+func (zp *ZonePrivate) Derive(label, context string) (*ZonePrivate, *math.Int) 
{
+       // get factory for given zone type
+       impl := zoneImpl[zp.Type]
+
+       // caclulate derived key
+       h := deriveH(zp.impl.Bytes(), label, context)
+       var derived ZonePrivateImpl
+       derived, h = zp.impl.Derive(h)
+
+       // assemble derived pivate key
+       dzp := &ZonePrivate{
+               ZoneKey{
+                       zp.Type,
+                       nil,
+                       nil,
+               },
+               derived,
+       }
+       zp.ZoneKey.KeyData = derived.Public().Bytes()
+       zp.ZoneKey.impl = impl.NewPublic()
+       zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+       return dzp, h
+}
+
+// ZoneSign data with a private key
+func (zp *ZonePrivate) Sign(data []byte) (sig *ZoneSignature, err error) {
+       return zp.impl.Sign(data)
+}
+
+// Public returns the associated public key
+func (zp *ZonePrivate) Public() *ZoneKey {
+       return &zp.ZoneKey
+}
+
+//----------------------------------------------------------------------
+// Zone key (public)
+//----------------------------------------------------------------------
+
+// ZoneKey represents the possible types of zone keys (PKEY, EDKEY,...)
+type ZoneKey struct {
+       Type    uint32 `json:"type" order:"big"`
+       KeyData []byte `json:"key" size:"(KeySize)"`
+
+       impl ZoneKeyImpl // reference to implementation
+}
+
+// NewZoneKey returns a new initialized ZoneKey instance
+func NewZoneKey(d []byte) (*ZoneKey, error) {
+       // read zone key from data
+       zk := new(ZoneKey)
+       if err := data.Unmarshal(zk, d); err != nil {
+               return nil, err
+       }
+       // initialize implementation
+       impl, ok := zoneImpl[zk.Type]
+       if !ok {
+               return nil, errors.New("unknown zone type")
+       }
+       zk.impl = impl.NewPublic()
+       zk.impl.Init(zk.KeyData)
+       return zk, nil
+}
+
+// KeySize returns the number of bytes of a key representation.
+// This method is used during serialization (Unmarshal).
+func (zk *ZoneKey) KeySize() uint {
+       if impl, ok := zoneImpl[zk.Type]; ok {
+               return impl.PublicSize
+       }
+       return 0
+}
+
+// Derive key (key blinding)
+func (zk *ZoneKey) Derive(label, context string) (*ZoneKey, *math.Int) {
+       h := deriveH(zk.KeyData, label, context)
+       var derived ZoneKeyImpl
+       derived, h = zk.impl.Derive(h)
+       return &ZoneKey{
+               Type:    zk.Type,
+               KeyData: derived.Bytes(),
+               impl:    derived,
+       }, h
+}
+
+// BlockKey returns the key for block en-/decryption
+func (zk *ZoneKey) BlockKey(label string, expires util.AbsoluteTime) (skey 
[]byte) {
+       return zk.impl.BlockKey(label, expires)
+}
+
+// Encrypt data
+func (zk *ZoneKey) Encrypt(data []byte, label string, expire 
util.AbsoluteTime) ([]byte, error) {
+       return zk.impl.Encrypt(data, label, expire)
+}
+
+// Decrypt data
+func (zk *ZoneKey) Decrypt(data []byte, label string, expire 
util.AbsoluteTime) ([]byte, error) {
+       return zk.impl.Decrypt(data, label, expire)
+}
+
+// Verify a zone signature
+func (zk *ZoneKey) Verify(data []byte, zs *ZoneSignature) (ok bool, err error) 
{
+       zk.withImpl()
+       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)
+       return util.EncodeBinaryToString(buf.Bytes())
+}
+
+// Bytes returns all bytes of a zone key
+func (zk *ZoneKey) Bytes() []byte {
+       data, _ := data.Marshal(zk)
+       return data
+}
+
+// Equal checks if two zone keys are equal
+func (zk *ZoneKey) Equal(k *ZoneKey) bool {
+       return bytes.Equal(zk.KeyData, k.KeyData)
+}
+
+// withImpl ensure that an implementation reference is available
+func (zk *ZoneKey) withImpl() {
+       if zk.impl == nil {
+               factory := zoneImpl[zk.Type]
+               zk.impl = factory.NewPublic()
+               zk.impl.Init(zk.KeyData)
+       }
+}
+
+//----------------------------------------------------------------------
+// Zone signature
+//----------------------------------------------------------------------
+
+type ZoneSignature struct {
+       ZoneKey
+       Signature []byte `size:"(SigSize)"` // signature data
+
+       impl ZoneSigImpl // reference to implementation
+}
+
+// NewZoneSignature returns a new initialized ZoneSignature instance
+func NewZoneSignature(d []byte) (*ZoneSignature, error) {
+       // read signature
+       sig := new(ZoneSignature)
+       if err := data.Unmarshal(sig, d); err != nil {
+               return nil, err
+       }
+       // initialize implementations
+       impl, ok := zoneImpl[sig.Type]
+       if !ok {
+               return nil, errors.New("unknown zone type")
+       }
+       // set signature implementation
+       zs := impl.NewSignature()
+       zs.Init(sig.Signature)
+       sig.impl = zs
+       // set public key implementation
+       zk := impl.NewPublic()
+       zk.Init(sig.KeyData)
+       sig.ZoneKey.impl = zk
+
+       return sig, nil
+}
+
+// SigSize returns the number of bytes of a signature that can be
+// verified with a given zone key. This method is used during
+// serialization (Unmarshal).
+func (zs *ZoneSignature) SigSize() uint {
+       if impl, ok := zoneImpl[zs.Type]; ok {
+               return impl.SignatureSize
+       }
+       return 0
+}
+
+// Key returns the associated zone key object
+func (zs *ZoneSignature) Key() *ZoneKey {
+       return &zs.ZoneKey
+}
+
+// Verify a signature
+func (zs *ZoneSignature) Verify(data []byte) (bool, error) {
+       return zs.ZoneKey.Verify(data, zs)
+}
+
+//----------------------------------------------------------------------
+// Helper functions
+//----------------------------------------------------------------------
+
+// deriveH derives an integer 'h' from the arguments.
+func deriveH(key []byte, label, context string) *math.Int {
+       prk := hkdf.Extract(sha512.New, key, []byte("key-derivation"))
+       data := append([]byte(label), []byte(context)...)
+       rdr := hkdf.Expand(sha256.New, prk, data)
+       b := make([]byte, 64)
+       rdr.Read(b)
+       return math.NewIntFromBytes(b)
 }
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
new file mode 100644
index 0000000..3153628
--- /dev/null
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -0,0 +1,245 @@
+// 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 crypto
+
+import (
+       "crypto/sha256"
+       "crypto/sha512"
+       "errors"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/math"
+       "golang.org/x/crypto/hkdf"
+       "golang.org/x/crypto/nacl/secretbox"
+)
+
+//======================================================================
+// EDKEY implementation for GNS zone crypto:
+// ----------------------------------------
+// Based on the Ed25519 curve. Private keys are defined by a seed
+// and signatures are based on EdDSA.
+//======================================================================
+
+// register our implementation
+func init() {
+       zoneImpl[ZONE_EDKEY] = &ZoneImplementation{
+               NewPrivate:    func() ZonePrivateImpl { return 
&EDKEYPrivateImpl{} },
+               PrivateSize:   32,
+               NewPublic:     func() ZoneKeyImpl { return &EDKEYPublicImpl{} },
+               PublicSize:    32,
+               NewSignature:  func() ZoneSigImpl { return &EDKEYSigImpl{} },
+               SignatureSize: 64,
+       }
+}
+
+//----------------------------------------------------------------------
+// Private key
+//----------------------------------------------------------------------
+
+// EDKEYPublicImpl implements the public key scheme.
+type EDKEYPublicImpl struct {
+       ztype uint32
+       pub   *ed25519.PublicKey
+}
+
+// Init instance from binary data. The data represents a big integer
+// (in big-endian notation) for the private scalar d.
+func (pk *EDKEYPublicImpl) Init(data []byte) error {
+       pk.ztype = ZONE_EDKEY
+       pk.pub = ed25519.NewPublicKeyFromBytes(data)
+       return nil
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (pk *EDKEYPublicImpl) Bytes() []byte {
+       return pk.pub.Bytes()
+}
+
+// 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) {
+       // limit to allowed value range
+       h = h.Mod(ed25519.GetCurve().N)
+       derived := pk.pub.Mult(h)
+       dPk := &EDKEYPublicImpl{
+               pk.ztype,
+               derived,
+       }
+       return dPk, h
+}
+
+// Encrypt binary data (of any size). Output can be larger than input
+func (pk *EDKEYPublicImpl) Encrypt(data []byte, label string, expires 
util.AbsoluteTime) (out []byte, err error) {
+       // derive key material for decryption
+       skey := pk.BlockKey(label, expires)
+
+       // En-/decrypt with XSalsa20-Poly1305 cipher
+       var key [32]byte
+       var nonce [24]byte
+       copy(key[:], skey[:32])
+       copy(nonce[:], skey[32:])
+       out = secretbox.Seal(nil, data, &nonce, &key)
+       return
+}
+
+// Decrypt binary data (of any size). Output can be smaller than input
+func (pk *EDKEYPublicImpl) Decrypt(data []byte, label string, expires 
util.AbsoluteTime) (out []byte, err error) {
+       // derive key material for decryption
+       skey := pk.BlockKey(label, expires)
+
+       // En-/decrypt with XSalsa20-Poly1305 cipher
+       var (
+               key   [32]byte
+               nonce [24]byte
+               ok    bool
+       )
+       copy(key[:], skey[:32])
+       copy(nonce[:], skey[32:])
+       if out, ok = secretbox.Open(nil, data, &nonce, &key); !ok {
+               err = errors.New("XSalsa20-Poly1305 open failed")
+       }
+       return
+}
+
+// Verify a signature for binary data
+func (pk *EDKEYPublicImpl) Verify(data []byte, zs *ZoneSignature) (ok bool, 
err error) {
+       var sig *ed25519.EdSignature
+       if sig, err = ed25519.NewEdSignatureFromBytes(zs.Signature); err != nil 
{
+               return
+       }
+       return pk.pub.EdVerify(data, sig)
+}
+
+// BlockKey return the symmetric key (and initialization vector) based on
+// label and expiration time.
+func (pk *EDKEYPublicImpl) BlockKey(label string, expires util.AbsoluteTime) 
(skey []byte) {
+       // generate symmetric key
+       skey = make([]byte, 56)
+       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])
+
+       // assemble initialization vector
+       iv := &struct {
+               Nonce      []byte            `size:"16"` // Nonce
+               Expiration util.AbsoluteTime ``          // Expiration time of 
block
+       }{
+               Nonce:      make([]byte, 16),
+               Expiration: expires,
+       }
+       prk = hkdf.Extract(sha512.New, kd, []byte("gns-xsalsa-ctx-iv"))
+       rdr = hkdf.Expand(sha256.New, prk, []byte(label))
+       rdr.Read(iv.Nonce)
+       buf, _ := data.Marshal(iv)
+       copy(skey[32:], buf)
+       return
+}
+
+//----------------------------------------------------------------------
+// Private key
+//----------------------------------------------------------------------
+
+// EDKEYPrivateImpl implements the private key scheme.
+type EDKEYPrivateImpl struct {
+       EDKEYPublicImpl
+
+       prv *ed25519.PrivateKey
+}
+
+// Init instance from binary data. The data represents a big integer
+// (in big-endian notation) for the private scalar d.
+func (pk *EDKEYPrivateImpl) Init(data []byte) error {
+       pk.prv = ed25519.NewPrivateKeyFromSeed(data)
+       pk.ztype = ZONE_EDKEY
+       pk.pub = pk.prv.Public()
+       return nil
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (pk *EDKEYPrivateImpl) Bytes() []byte {
+       return pk.prv.Bytes()
+}
+
+// Public returns the associate public key implementation.
+func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
+       return &pk.EDKEYPublicImpl
+}
+
+// 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) {
+       // limit to allowed value range
+       h = h.Mod(ed25519.GetCurve().N)
+       derived := pk.prv.Mult(h)
+       dPk := &EDKEYPrivateImpl{
+               EDKEYPublicImpl{
+                       pk.ztype,
+                       derived.Public(),
+               },
+               derived,
+       }
+       return dPk, h
+}
+
+// Sign binary data
+func (pk *EDKEYPrivateImpl) Sign(data []byte) (*ZoneSignature, error) {
+       s, err := pk.prv.EdSign(data)
+       if err != nil {
+               return nil, err
+       }
+       sd := s.Bytes()
+       sigImpl := new(EDKEYSigImpl)
+       sigImpl.Init(sd)
+       sig := &ZoneSignature{
+               ZoneKey{
+                       Type:    pk.ztype,
+                       KeyData: pk.pub.Bytes(),
+               },
+               sd,
+               sigImpl,
+       }
+       return sig, nil
+}
+
+//----------------------------------------------------------------------
+// Signature
+//----------------------------------------------------------------------
+
+// ZoneSigImpl defines the methods for a signature object.
+type EDKEYSigImpl struct {
+       sig *ed25519.EcSignature
+}
+
+// Init instance from binary data. The data represents a big integers
+// R and S of the signature.
+func (s *EDKEYSigImpl) Init(data []byte) (err error) {
+       s.sig, err = ed25519.NewEcSignatureFromBytes(data)
+       return
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (s *EDKEYSigImpl) Bytes() []byte {
+       return s.sig.Bytes()
+}
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
new file mode 100644
index 0000000..fa319a4
--- /dev/null
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -0,0 +1,241 @@
+// 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 crypto
+
+import (
+       "crypto/aes"
+       "crypto/cipher"
+       "crypto/sha256"
+       "crypto/sha512"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/math"
+       "golang.org/x/crypto/hkdf"
+)
+
+//======================================================================
+// PKEY implementation for GNS zone crypto:
+// ----------------------------------------
+// Based on the Ed25519 curve. Private keys are defined by a scalar
+// and signatures are based on a deterministic variant of ECDSA.
+//======================================================================
+
+// register our implementation
+func init() {
+       zoneImpl[ZONE_PKEY] = &ZoneImplementation{
+               NewPrivate:    func() ZonePrivateImpl { return 
&PKEYPrivateImpl{} },
+               PrivateSize:   32,
+               NewPublic:     func() ZoneKeyImpl { return &PKEYPublicImpl{} },
+               PublicSize:    32,
+               NewSignature:  func() ZoneSigImpl { return &PKEYSigImpl{} },
+               SignatureSize: 64,
+       }
+}
+
+//----------------------------------------------------------------------
+// Private key
+//----------------------------------------------------------------------
+
+// PKEYPublicImpl implements the public key scheme.
+type PKEYPublicImpl struct {
+       ztype uint32
+       pub   *ed25519.PublicKey
+}
+
+// Init instance from binary data. The data represents a big integer
+// (in big-endian notation) for the private scalar d.
+func (pk *PKEYPublicImpl) Init(data []byte) error {
+       pk.ztype = ZONE_PKEY
+       pk.pub = ed25519.NewPublicKeyFromBytes(data)
+       return nil
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (pk *PKEYPublicImpl) Bytes() []byte {
+       return pk.pub.Bytes()
+}
+
+// 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) {
+       // limit to allowed value range
+       h = h.Mod(ed25519.GetCurve().N)
+       derived := pk.pub.Mult(h)
+       dPk := &PKEYPublicImpl{
+               pk.ztype,
+               derived,
+       }
+       return dPk, h
+}
+
+// Encrypt binary data (of any size). Output can be larger than input
+func (pk *PKEYPublicImpl) Encrypt(data []byte, label string, expires 
util.AbsoluteTime) ([]byte, error) {
+       return pk.cipher(true, data, label, expires)
+}
+
+// Decrypt binary data (of any size). Output can be smaller than input
+func (pk *PKEYPublicImpl) Decrypt(data []byte, label string, expires 
util.AbsoluteTime) ([]byte, error) {
+       return pk.cipher(false, data, label, expires)
+}
+
+// Verify a signature for binary data
+func (pk *PKEYPublicImpl) Verify(data []byte, zs *ZoneSignature) (ok bool, err 
error) {
+       var sig *ed25519.EcSignature
+       if sig, err = ed25519.NewEcSignatureFromBytes(zs.Signature); err != nil 
{
+               return
+       }
+       return pk.pub.EcVerify(data, sig)
+}
+
+// BlockKey return the symmetric key (and initialization vector) based on
+// label and expiration time.
+func (pk *PKEYPublicImpl) BlockKey(label string, expires util.AbsoluteTime) 
(skey []byte) {
+       // generate symmetric key
+       skey = make([]byte, 48)
+       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])
+
+       // assemble initialization vector
+       iv := &struct {
+               Nonce      []byte            `size:"4"`    // 32 bit Nonce
+               Expiration util.AbsoluteTime ``            // Expiration time 
of block
+               Counter    uint32            `order:"big"` // Block counter
+       }{
+               Nonce:      make([]byte, 4),
+               Expiration: expires,
+               Counter:    1,
+       }
+       prk = hkdf.Extract(sha512.New, kd, []byte("gns-aes-ctx-iv"))
+       rdr = hkdf.Expand(sha256.New, prk, []byte(label))
+       rdr.Read(iv.Nonce)
+       buf, _ := data.Marshal(iv)
+       copy(skey[32:], buf)
+       return
+}
+
+// cipher implements symmetric en/-decryption (for block data).
+func (pk *PKEYPublicImpl) cipher(encrypt bool, data []byte, label string, 
expires util.AbsoluteTime) (out []byte, err error) {
+       // derive key material for decryption
+       skey := pk.BlockKey(label, expires)
+
+       // En-/decrypt with AES CTR stream cipher
+       var blk cipher.Block
+       if blk, err = aes.NewCipher(skey[:32]); err != nil {
+               return
+       }
+       stream := cipher.NewCTR(blk, skey[32:])
+       out = make([]byte, len(data))
+       stream.XORKeyStream(out, data)
+       return
+}
+
+//----------------------------------------------------------------------
+// Private key
+//----------------------------------------------------------------------
+
+// PKEYPrivateImpl implements the private key scheme.
+type PKEYPrivateImpl struct {
+       PKEYPublicImpl
+
+       prv *ed25519.PrivateKey
+}
+
+// Init instance from binary data. The data represents a big integer
+// (in big-endian notation) for the private scalar d.
+func (pk *PKEYPrivateImpl) Init(data []byte) error {
+       d := math.NewIntFromBytes(data)
+       pk.prv = ed25519.NewPrivateKeyFromD(d)
+       pk.ztype = ZONE_PKEY
+       pk.pub = pk.prv.Public()
+       return nil
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (pk *PKEYPrivateImpl) Bytes() []byte {
+       return pk.prv.Bytes()
+}
+
+// Public returns the associate public key implementation.
+func (pk *PKEYPrivateImpl) Public() ZoneKeyImpl {
+       return &pk.PKEYPublicImpl
+}
+
+// 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) {
+       // limit to allowed value range
+       h = h.Mod(ed25519.GetCurve().N)
+       derived := pk.prv.Mult(h)
+       dPk := &PKEYPrivateImpl{
+               PKEYPublicImpl{
+                       pk.ztype,
+                       derived.Public(),
+               },
+               derived,
+       }
+       return dPk, h
+}
+
+// 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
+       }
+       sd := s.Bytes()
+       sigImpl := new(PKEYSigImpl)
+       sigImpl.Init(sd)
+       sig := &ZoneSignature{
+               ZoneKey{
+                       Type:    pk.ztype,
+                       KeyData: pk.pub.Bytes(),
+               },
+               sd,
+               sigImpl,
+       }
+       return sig, nil
+}
+
+//----------------------------------------------------------------------
+// Signature
+//----------------------------------------------------------------------
+
+// ZoneSigImpl defines the methods for a signature object.
+type PKEYSigImpl struct {
+       sig *ed25519.EcSignature
+}
+
+// Init instance from binary data. The data represents a big integers
+// R and S of the signature.
+func (s *PKEYSigImpl) Init(data []byte) (err error) {
+       s.sig, err = ed25519.NewEcSignatureFromBytes(data)
+       return
+}
+
+// Bytes returns a binary representation of the instance suitable for
+// consumption in 'Init()'.
+func (s *PKEYSigImpl) Bytes() []byte {
+       return s.sig.Bytes()
+}
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index ae39a2b..62a2dfd 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -20,100 +20,124 @@ package crypto
 
 import (
        "bytes"
+       "crypto/sha256"
+       "crypto/sha512"
        "encoding/hex"
+       "gnunet/util"
        "testing"
+       "time"
 
-       "github.com/bfix/gospel/crypto/ed25519"
-)
-
-var (
-       PUB = []byte{
-               0x23, 0xd8, 0x9a, 0x29, 0xda, 0x0f, 0x68, 0x08,
-               0xc6, 0xb6, 0xd5, 0xe5, 0x9c, 0xdd, 0x6a, 0x6f,
-               0xcf, 0x3e, 0x2b, 0xb0, 0x06, 0xf4, 0x66, 0xd5,
-               0x42, 0x3a, 0x93, 0x5d, 0x6b, 0x4d, 0x7e, 0x10,
-       }
-       LABEL = "home"
+       "golang.org/x/crypto/hkdf"
 )
 
 func TestDeriveBlockKey(t *testing.T) {
        var (
-               SKEY = []byte{
+               PUB = []byte{
+                       0x23, 0xd8, 0x9a, 0x29, 0xda, 0x0f, 0x68, 0x08,
+                       0xc6, 0xb6, 0xd5, 0xe5, 0x9c, 0xdd, 0x6a, 0x6f,
+                       0xcf, 0x3e, 0x2b, 0xb0, 0x06, 0xf4, 0x66, 0xd5,
+                       0x42, 0x3a, 0x93, 0x5d, 0x6b, 0x4d, 0x7e, 0x10,
+               }
+               LABEL  = "home"
+               EXPIRE = util.NewAbsoluteTime(time.Unix(1643714700060589, 0))
+               SKEY   = []byte{
                        0x0c, 0xf7, 0x4d, 0x44, 0x19, 0xe4, 0xac, 0x52,
                        0x3d, 0x14, 0xf4, 0x9b, 0x09, 0x6c, 0x52, 0xb6,
                        0xb3, 0xf5, 0x06, 0x68, 0x98, 0x26, 0xa5, 0xea,
                        0x06, 0x93, 0xfd, 0x4d, 0x80, 0xab, 0xf0, 0x44,
-
-                       0xa7, 0x6c, 0xc7, 0x81, 0x07, 0x3b, 0x94, 0x49,
-                       0x91, 0x8e, 0xe0, 0x4c, 0x3a, 0x07, 0x56, 0x22,
-                       0xe6, 0x95, 0xc3, 0xc7, 0x31, 0xc0, 0xfe, 0x62,
-                       0xc3, 0x07, 0xe5, 0x9f, 0x96, 0xa2, 0xd0, 0x48,
                }
                IV = []byte{
-                       0x04, 0x41, 0xfc, 0xfc, 0x96, 0x5f, 0x2e, 0xa7,
-                       0x35, 0xea, 0x59, 0xd8, 0x16, 0xd2, 0xfb, 0xc8,
-
-                       0x6e, 0xdc, 0xb3, 0xdf, 0x0c, 0x28, 0xbb, 0x14,
-                       0xc4, 0x51, 0x1c, 0x89, 0x9f, 0xb6, 0xdd, 0xfc,
+                       0x04, 0x41, 0xfc, 0xfc,
+                       0x1b, 0x1f, 0xb2, 0xee, 0x6f, 0x27, 0x85, 0x40,
+                       0x00, 0x00, 0x00, 0x01,
                }
        )
 
-       iv, skey := DeriveBlockKey(LABEL, ed25519.NewPublicKeyFromBytes(PUB))
+       // create and initialize new public zone key (PKEY)
+       zkey := new(PKEYPublicImpl)
+       zkey.Init(PUB)
 
-       if bytes.Compare(IV[:16], iv.AESIv) != 0 {
-               t.Logf("AES_IV(computed) = %s\n", hex.EncodeToString(iv.AESIv))
-               t.Logf("AES_IV(expected) = %s\n", hex.EncodeToString(IV[:16]))
+       // derive and check a key for symmetric cipher
+       skey := zkey.BlockKey(LABEL, EXPIRE)
+       if !bytes.Equal(IV, skey[32:]) {
+               t.Logf("AES_IV(computed) = %s\n", hex.EncodeToString(skey[32:]))
+               t.Logf("AES_IV(expected) = %s\n", hex.EncodeToString(IV))
                t.Fatal("AES IV mismatch")
        }
-       if bytes.Compare(IV[16:], iv.TwofishIv) != 0 {
-               t.Logf("Twofish_IV(computed) = %s\n", 
hex.EncodeToString(iv.TwofishIv))
-               t.Logf("Twofish_IV(expected) = %s\n", 
hex.EncodeToString(IV[16:]))
-               t.Fatal("Twofish IV mismatch")
-       }
-
-       if bytes.Compare(SKEY[:32], skey.AESKey) != 0 {
-               t.Logf("AES_KEY(computed) = %s\n", 
hex.EncodeToString(skey.AESKey))
-               t.Logf("AES_KEY(expected) = %s\n", 
hex.EncodeToString(SKEY[:32]))
+       if !bytes.Equal(SKEY, skey[:32]) {
+               t.Logf("AES_KEY(computed) = %s\n", 
hex.EncodeToString(skey[:32]))
+               t.Logf("AES_KEY(expected) = %s\n", hex.EncodeToString(SKEY))
                t.Fatal("AES KEY mismatch")
        }
-       if bytes.Compare(SKEY[32:], skey.TwofishKey) != 0 {
-               t.Logf("Twofish_KEY(computed) = %s\n", 
hex.EncodeToString(skey.TwofishKey))
-               t.Logf("Twofish_KEY(expected) = %s\n", 
hex.EncodeToString(SKEY[32:]))
-               t.Fatal("Twofish KEY mismatch")
-       }
 }
 
 func TestDecryptBlock(t *testing.T) {
        var (
                DATA = []byte{
-                       0xf7, 0x61, 0x5f, 0x1f, 0xbc, 0x08, 0xb4, 0x32,
-                       0x46, 0xdb, 0x09, 0xb7, 0x57, 0x2d, 0x1c, 0x46,
-                       0xe2, 0xd8, 0xbf, 0x74, 0x17, 0x4e, 0x9a, 0x1e,
-                       0xf0, 0x7a, 0xed, 0x45, 0x58, 0x62, 0x60, 0xc9,
-                       0xd7, 0xbd, 0x0d, 0x34, 0x6e, 0x08, 0xd8, 0x65,
-                       0x69, 0x5c, 0x6d, 0x0b, 0xc6, 0x36, 0x73, 0x4c,
-                       0xac, 0xd1, 0xf2, 0x38, 0x5e, 0x63, 0x86, 0x80,
-                       0x54, 0xa2, 0x1e, 0x94, 0x77, 0xd0, 0xa0, 0x50,
-                       0x96, 0xbb, 0x33, 0x4b,
-               }
-               OUT = []byte{
-                       0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0xaf, 0x87,
-                       0x00, 0x5b, 0x91, 0x40, 0x00, 0x00, 0x00, 0x17,
-                       0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00,
-                       0x00, 0x0a, 0x04, 0x6d, 0x61, 0x69, 0x6c, 0x0a,
-                       0x68, 0x6f, 0x69, 0x2d, 0x70, 0x6f, 0x6c, 0x6c,
-                       0x6f, 0x69, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x02, 0x00, 0x34, 0xe5, 0x3b,
+                       0xe1, 0x93, 0x79, 0x91, 0x00, 0x00, 0x00, 0x04,
+                       0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                       0x01, 0x02, 0x03, 0x04, 0x00, 0x5c, 0xe4, 0xa5,
+                       0x39, 0x4a, 0xd9, 0x91, 0x00, 0x00, 0x00, 0x24,
+                       0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                       0x00, 0x01, 0x00, 0x00, 0x0e, 0x60, 0x1b, 0xe4,
+                       0x2e, 0xb5, 0x7f, 0xb4, 0x69, 0x76, 0x10, 0xcf,
+                       0x3a, 0x3b, 0x18, 0x34, 0x7b, 0x65, 0xa3, 0x3f,
+                       0x02, 0x5b, 0x5b, 0x17, 0x4a, 0xbe, 0xfb, 0x30,
+                       0x80, 0x7b, 0xfe, 0xcf, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00,
                }
+               OUT = []byte{
+                       0x00, 0xe4, 0x83, 0x7e, 0xb5, 0xd0, 0x4f, 0x92,
+                       0x90, 0x3d, 0xe4, 0xb5, 0x23, 0x4e, 0x8c, 0xca,
+                       0xc5, 0x73, 0x6c, 0x97, 0x93, 0x37, 0x9a, 0x59,
+                       0xc3, 0x33, 0x75, 0xfc, 0x89, 0x51, 0xac, 0xa2,
+                       0xeb, 0x7a, 0xad, 0x06, 0x7b, 0xf9, 0xaf, 0x60,
+                       0xbf, 0x26, 0x75, 0x86, 0x46, 0xa1, 0x7f, 0x5e,
+                       0x5c, 0x3b, 0x62, 0x15, 0xf9, 0x40, 0x79, 0x54,
+                       0x5b, 0x1c, 0x4d, 0x4f, 0x1b, 0x2e, 0xbb, 0x22,
+                       0xc2, 0xb4, 0xda, 0xd4, 0x41, 0x26, 0x81, 0x7b,
+                       0x6f, 0x00, 0x15, 0x30, 0xd4, 0x76, 0x40, 0x1d,
+                       0xd6, 0x7a, 0xc0, 0x14, 0x85, 0x54, 0xe8, 0x06,
+                       0x35, 0x3d, 0xa9, 0xe4, 0x29, 0x80, 0x79, 0xf3,
+                       0xe1, 0xb1, 0x69, 0x42, 0xc4, 0x8d, 0x90, 0xc4,
+                       0x36, 0x0c, 0x61, 0x23, 0x8c, 0x40, 0xd9, 0xd5,
+                       0x29, 0x11, 0xae, 0xa5, 0x2c, 0xc0, 0x03, 0x7a,
+                       0xc7, 0x16, 0x0b, 0xb3, 0xcf, 0x5b, 0x2f, 0x4a,
+                       0x72, 0x2f, 0xd9, 0x6b,
+               }
+               LABEL  = "test"
+               EXPIRE = util.AbsoluteTime{
+                       Val: uint64(14888744139323793),
+               }
+               PUB = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x00,
+
+                       // public key
+                       0x67, 0x7c, 0x47, 0x7d, 0x2d, 0x93, 0x09, 0x7c,
+                       0x85, 0xb1, 0x95, 0xc6, 0xf9, 0x6d, 0x84, 0xff,
+                       0x61, 0xf5, 0x98, 0x2c, 0x2c, 0x4f, 0xe0, 0x2d,
+                       0x5a, 0x11, 0xfe, 0xdf, 0xb0, 0xc2, 0x90, 0x1f,
+               }
        )
 
-       out, err := DecryptBlock(DATA, ed25519.NewPublicKeyFromBytes(PUB), 
LABEL)
+       // create and initialize new public zone key (PKEY)
+       zkey, err := NewZoneKey(PUB)
        if err != nil {
                t.Fatal(err)
        }
-       if bytes.Compare(out, OUT) != 0 {
+
+       out, err := zkey.Encrypt(DATA, LABEL, EXPIRE)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(out, OUT) {
                t.Logf("Decrypt(computed) = %s\n", hex.EncodeToString(out))
                t.Logf("Decrypt(expected) = %s\n", hex.EncodeToString(OUT))
                t.Fatal("Decryptions failed")
@@ -136,33 +160,239 @@ func TestVerifyBlock(t *testing.T) {
                        0xf0, 0x4e, 0x3b, 0x70,
                }
                SIG = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x00,
+
+                       // public key
+                       0x26, 0x84, 0x1b, 0x24, 0x35, 0xa4, 0x63, 0xe9,
+                       0xf0, 0x48, 0xae, 0x3e, 0xf7, 0xe8, 0x1b, 0xca,
+                       0x55, 0x9f, 0x4c, 0x1e, 0x16, 0x18, 0xa6, 0xd3,
+                       0x5b, 0x91, 0x0d, 0x54, 0x31, 0x6e, 0xbf, 0x97,
+
+                       // signature
                        0x09, 0xc9, 0x6a, 0xda, 0x69, 0xce, 0x7c, 0x91,
                        0xbd, 0xa4, 0x59, 0xdc, 0xc9, 0x76, 0xf4, 0x6c,
                        0x62, 0xb7, 0x79, 0x3f, 0x94, 0xb2, 0xf6, 0xf0,
                        0x90, 0x17, 0x4e, 0x2f, 0x68, 0x49, 0xf8, 0xcc,
-
                        0x0b, 0x77, 0x32, 0x32, 0x28, 0x77, 0x2d, 0x2a,
                        0x31, 0x31, 0xc1, 0x2c, 0x44, 0x18, 0xf2, 0x5f,
                        0x1a, 0xe9, 0x8b, 0x2e, 0x65, 0xca, 0x1d, 0xe8,
                        0x22, 0x82, 0x6a, 0x06, 0xe0, 0x6a, 0x5a, 0xe5,
                }
-               PUB = []byte{
-                       0x26, 0x84, 0x1b, 0x24, 0x35, 0xa4, 0x63, 0xe9,
-                       0xf0, 0x48, 0xae, 0x3e, 0xf7, 0xe8, 0x1b, 0xca,
-                       0x55, 0x9f, 0x4c, 0x1e, 0x16, 0x18, 0xa6, 0xd3,
-                       0x5b, 0x91, 0x0d, 0x54, 0x31, 0x6e, 0xbf, 0x97,
-               }
        )
-       sig, err := ed25519.NewEcSignatureFromBytes(SIG)
+
+       // instantiate and initialize signature
+       sig, err := NewZoneSignature(SIG)
        if err != nil {
                t.Fatal(err)
        }
-       dkey := ed25519.NewPublicKeyFromBytes(PUB)
-       ok, err := dkey.EcVerify(SIGNED, sig)
+       // verify signature
+       ok, err := sig.Verify(SIGNED)
        if err != nil {
                t.Fatal(err)
        }
        if !ok {
-               t.Fatal("EcDSA verify failed")
+               t.Fatal("signature verify failed")
+       }
+}
+
+func TestDeriveH(t *testing.T) {
+       var (
+               D = []byte{
+                       // private scalar (big-endian)
+                       0x74, 0x50, 0xf7, 0x1d, 0xef, 0x64, 0x11, 0xe0,
+                       0xab, 0x0e, 0x6a, 0x1d, 0xfd, 0x1d, 0x9c, 0xcd,
+                       0x0e, 0xaf, 0x71, 0x95, 0x24, 0x94, 0xcc, 0xf5,
+                       0x1b, 0x85, 0xff, 0xac, 0x5d, 0xb0, 0x93, 0xc8,
+               }
+               PUB = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x00,
+                       // public key data
+                       0x23, 0xd8, 0x9a, 0x29, 0xda, 0x0f, 0x68, 0x08,
+                       0xc6, 0xb6, 0xd5, 0xe5, 0x9c, 0xdd, 0x6a, 0x6f,
+                       0xcf, 0x3e, 0x2b, 0xb0, 0x06, 0xf4, 0x66, 0xd5,
+                       0x42, 0x3a, 0x93, 0x5d, 0x6b, 0x4d, 0x7e, 0x10,
+               }
+
+               ID      = 
"000G0013V2D2KPGFD04CDDPNWPEDTTKFSWZ2QC06YHKDAGHTJDEPPKBY20"
+               LABEL   = "home"
+               CONTEXT = "gns"
+
+               H = []byte{
+                       0x07, 0x1e, 0xfc, 0xa7, 0xdb, 0x28, 0x50, 0xbd,
+                       0x6f, 0x35, 0x4e, 0xbf, 0xe3, 0x8c, 0x5b, 0xbf,
+                       0xd6, 0xba, 0x2f, 0x80, 0x5c, 0xd8, 0xd3, 0xb5,
+                       0x4e, 0xdd, 0x7f, 0x3d, 0xd0, 0x73, 0x0d, 0x1a,
+               }
+               Q = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x00,
+                       // derived public key data
+                       0x9f, 0x27, 0xad, 0x25, 0xb5, 0x95, 0x4a, 0x46,
+                       0x7b, 0xc6, 0x5a, 0x67, 0x6b, 0x7a, 0x6d, 0x23,
+                       0xb2, 0xef, 0x30, 0x0f, 0x7f, 0xc7, 0x00, 0x58,
+                       0x05, 0x9e, 0x7f, 0x29, 0xe5, 0x94, 0xb5, 0xc1,
+               }
+               QUERY = []byte{
+                       0xa9, 0x1a, 0x2c, 0x46, 0xf1, 0x98, 0x35, 0x50,
+                       0x4f, 0x4e, 0x96, 0x78, 0x2d, 0x77, 0xd1, 0x3b,
+                       0x9d, 0x4e, 0x61, 0xf3, 0x50, 0xe2, 0xe6, 0xa5,
+                       0xc2, 0xd1, 0x36, 0xc1, 0xf1, 0x37, 0x94, 0x79,
+                       0x19, 0xe9, 0xab, 0x2b, 0xae, 0xb5, 0xb9, 0x79,
+                       0xe9, 0x1e, 0xf2, 0x6a, 0xaa, 0x54, 0x81, 0x65,
+                       0xac, 0xb2, 0xec, 0xca, 0x8e, 0x30, 0x76, 0x1c,
+                       0xc2, 0x1b, 0xbe, 0x89, 0x0b, 0x34, 0x6d, 0xa1,
+               }
+       )
+
+       // create private key from scalar
+       prv, err := NewZonePrivate(ZONE_PKEY, D)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       // derive and checkpublic key
+       pub := prv.Public()
+       if !bytes.Equal(pub.Bytes(), PUB) {
+               t.Fatal("wrong public key")
+       }
+
+       // test ID
+       id := pub.ID()
+       if ID != id {
+               t.Logf("id = %s\n", id)
+               t.Logf("ID = %s\n", ID)
+               t.Fatal("wrong ID")
+       }
+
+       // test key derivation
+       dpub, h := pub.Derive(LABEL, CONTEXT)
+       if !bytes.Equal(h.Bytes(), H) {
+               if testing.Verbose() {
+                       t.Logf("H(computed) = %s\n", 
hex.EncodeToString(h.Bytes()))
+                       t.Logf("H(expected) = %s\n", hex.EncodeToString(H))
+               }
+               t.Fatal("H mismatch")
+       }
+       // test derived key
+       q := dpub.Bytes()
+       if !bytes.Equal(q, Q) {
+               if testing.Verbose() {
+                       t.Logf("derived (computed) = %s\n", 
hex.EncodeToString(q))
+                       t.Logf("derived (expected) = %s\n", 
hex.EncodeToString(Q))
+               }
+               t.Fatal("x-coordinate mismatch")
+       }
+
+       // test query
+       out := sha512.Sum512(dpub.Bytes())
+       if !bytes.Equal(out[:], QUERY) {
+               if testing.Verbose() {
+                       t.Log("query(computed) = " + hex.EncodeToString(out[:]))
+                       t.Log("query(expected) = " + hex.EncodeToString(QUERY))
+               }
+               t.Fatal("Query mismatch")
+       }
+}
+
+func TestHKDF_gnunet(t *testing.T) {
+
+       var (
+               // SALT as defined in GNUnet
+               salt = []byte("key-derivation")
+               // expected PRK (as dumped in GNUnet)
+               PRK = []byte{
+                       0xEB, 0xFE, 0x63, 0xBA, 0x68, 0x2D, 0xA5, 0x5C,
+                       0xF8, 0x37, 0xCE, 0x8F, 0x94, 0x3B, 0x01, 0x44,
+                       0x1B, 0xF9, 0x67, 0x3D, 0xFC, 0x91, 0xED, 0x61,
+                       0x79, 0x94, 0xE8, 0x2A, 0x62, 0x0A, 0xE8, 0x6E,
+                       0x59, 0xDB, 0x56, 0x63, 0x80, 0x94, 0x63, 0xAC,
+                       0x8D, 0x35, 0xE2, 0xEA, 0xBA, 0xE6, 0xF3, 0xE8,
+                       0xC1, 0x4B, 0xC9, 0x4F, 0xBD, 0xE3, 0xE6, 0x61,
+                       0x01, 0xB3, 0xB2, 0x1C, 0x6F, 0x19, 0x73, 0x8B,
+               }
+               info = []byte("master-homegns")
+               // expected result (as dumped in GNUnet)
+               OKM = []byte{
+                       0x30, 0x86, 0x34, 0x7F, 0x2E, 0x12, 0xD7, 0x65,
+                       0x35, 0x70, 0x44, 0xE2, 0xF6, 0x9B, 0x84, 0x59,
+                       0x6E, 0xE1, 0x7F, 0x62, 0x93, 0xAD, 0xAE, 0x56,
+                       0x50, 0x6A, 0xA6, 0xD6, 0x8D, 0x39, 0x39, 0x95,
+               }
+       )
+       prk := hkdf.Extract(sha512.New, pub.Bytes(), salt)
+       if testing.Verbose() {
+               t.Log("PRK(computed) = " + hex.EncodeToString(prk))
+       }
+       if !bytes.Equal(prk, PRK) {
+               t.Log("PRK(expected) = " + hex.EncodeToString(PRK))
+               t.Fatal("PRK mismatch")
+       }
+
+       rdr := hkdf.Expand(sha256.New, prk, info)
+       okm := make([]byte, len(OKM))
+       rdr.Read(okm)
+       if testing.Verbose() {
+               t.Log("OKM(computed) = " + hex.EncodeToString(okm))
+       }
+       if !bytes.Equal(okm, OKM) {
+               t.Log("OKM(expected) = " + hex.EncodeToString(OKM))
+               t.Fatal("OKM mismatch")
+       }
+}
+
+func TestHDKF(t *testing.T) {
+       var (
+               ikm = []byte{
+                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+               }
+               salt = []byte{
+                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                       0x08, 0x09, 0x0a, 0x0b, 0x0c,
+               }
+               PRK = []byte{
+                       0x66, 0x57, 0x99, 0x82, 0x37, 0x37, 0xde, 0xd0,
+                       0x4a, 0x88, 0xe4, 0x7e, 0x54, 0xa5, 0x89, 0x0b,
+                       0xb2, 0xc3, 0xd2, 0x47, 0xc7, 0xa4, 0x25, 0x4a,
+                       0x8e, 0x61, 0x35, 0x07, 0x23, 0x59, 0x0a, 0x26,
+                       0xc3, 0x62, 0x38, 0x12, 0x7d, 0x86, 0x61, 0xb8,
+                       0x8c, 0xf8, 0x0e, 0xf8, 0x02, 0xd5, 0x7e, 0x2f,
+                       0x7c, 0xeb, 0xcf, 0x1e, 0x00, 0xe0, 0x83, 0x84,
+                       0x8b, 0xe1, 0x99, 0x29, 0xc6, 0x1b, 0x42, 0x37,
+               }
+               info = []byte{
+                       0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 
0xf9,
+               }
+               OKM = []byte{
+                       0x83, 0x23, 0x90, 0x08, 0x6c, 0xda, 0x71, 0xfb,
+                       0x47, 0x62, 0x5b, 0xb5, 0xce, 0xb1, 0x68, 0xe4,
+                       0xc8, 0xe2, 0x6a, 0x1a, 0x16, 0xed, 0x34, 0xd9,
+                       0xfc, 0x7f, 0xe9, 0x2c, 0x14, 0x81, 0x57, 0x93,
+                       0x38, 0xda, 0x36, 0x2c, 0xb8, 0xd9, 0xf9, 0x25,
+                       0xd7, 0xcb,
+               }
+       )
+
+       prk := hkdf.Extract(sha512.New, ikm, salt)
+       if testing.Verbose() {
+               t.Log("PRK(computed) = " + hex.EncodeToString(prk))
+       }
+       if !bytes.Equal(prk, PRK) {
+               t.Log("PRK(expected) = " + hex.EncodeToString(PRK))
+               t.Fatal("PRK mismatch")
+       }
+
+       rdr := hkdf.Expand(sha512.New, prk, info)
+       okm := make([]byte, len(OKM))
+       rdr.Read(okm)
+       if testing.Verbose() {
+               t.Log("OKM(computed) = " + hex.EncodeToString(okm))
+       }
+       if !bytes.Equal(okm, OKM) {
+               t.Log("OKM(expected) = " + hex.EncodeToString(OKM))
+               t.Fatal("OKM mismatch")
        }
 }
diff --git a/src/gnunet/crypto/key_derivation.go 
b/src/gnunet/crypto/key_derivation.go
deleted file mode 100644
index 1481f70..0000000
--- a/src/gnunet/crypto/key_derivation.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 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 crypto
-
-import (
-       "crypto/sha256"
-       "crypto/sha512"
-
-       "github.com/bfix/gospel/crypto/ed25519"
-       "github.com/bfix/gospel/math"
-       "golang.org/x/crypto/hkdf"
-)
-
-// Curve parameters
-var (
-       ED25519_N = ed25519.GetCurve().N
-)
-
-// DeriveH derives an integer 'h' from the arguments.
-func DeriveH(pub *ed25519.PublicKey, label, context string) *math.Int {
-       prk := hkdf.Extract(sha512.New, pub.Bytes(), []byte("key-derivation"))
-       data := append([]byte(label), []byte(context)...)
-       rdr := hkdf.Expand(sha256.New, prk, data)
-       b := make([]byte, 64)
-       rdr.Read(b)
-       return math.NewIntFromBytes(b).Mod(ED25519_N)
-}
-
-// DerivePublicKey "shifts" a public key 'Q' to a new point 'P' where
-// P = h*Q with 'h' being a factor derived from the arguments.
-func DerivePublicKey(pub *ed25519.PublicKey, label string, context string) 
*ed25519.PublicKey {
-       h := DeriveH(pub, label, context)
-       return pub.Mult(h)
-}
diff --git a/src/gnunet/crypto/key_derivation_test.go 
b/src/gnunet/crypto/key_derivation_test.go
deleted file mode 100644
index 4692c19..0000000
--- a/src/gnunet/crypto/key_derivation_test.go
+++ /dev/null
@@ -1,228 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 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 crypto
-
-import (
-       "bytes"
-       "crypto/sha256"
-       "crypto/sha512"
-       "encoding/hex"
-       "testing"
-
-       "github.com/bfix/gospel/crypto/ed25519"
-       "github.com/bfix/gospel/math"
-       "gnunet/util"
-       "golang.org/x/crypto/hkdf"
-)
-
-func TestDeriveH(t *testing.T) {
-       var (
-               D = []byte{
-                       0x74, 0x50, 0xf7, 0x1d, 0xef, 0x64, 0x11, 0xe0,
-                       0xab, 0x0e, 0x6a, 0x1d, 0xfd, 0x1d, 0x9c, 0xcd,
-                       0x0e, 0xaf, 0x71, 0x95, 0x24, 0x94, 0xcc, 0xf5,
-                       0x1b, 0x85, 0xff, 0xac, 0x5d, 0xb0, 0x93, 0xc8,
-               }
-
-               ID      = "4FC9MAET1XM0HHNPTQJSSQBADZ7KWAXG0VT6DNA27A9NTTTDFR80"
-               LABEL   = "home"
-               CONTEXT = "gns"
-
-               H = []byte{
-                       0x07, 0x1e, 0xfc, 0xa7, 0xdb, 0x28, 0x50, 0xbd,
-                       0x6f, 0x35, 0x4e, 0xbf, 0xe3, 0x8c, 0x5b, 0xbf,
-                       0xd6, 0xba, 0x2f, 0x80, 0x5c, 0xd8, 0xd3, 0xb5,
-                       0x4e, 0xdd, 0x7f, 0x3d, 0xd0, 0x73, 0x0d, 0x1a,
-               }
-               Q = []byte{
-                       0x9f, 0x27, 0xad, 0x25, 0xb5, 0x95, 0x4a, 0x46,
-                       0x7b, 0xc6, 0x5a, 0x67, 0x6b, 0x7a, 0x6d, 0x23,
-                       0xb2, 0xef, 0x30, 0x0f, 0x7f, 0xc7, 0x00, 0x58,
-                       0x05, 0x9e, 0x7f, 0x29, 0xe5, 0x94, 0xb5, 0xc1,
-               }
-               QUERY = []byte{
-                       0xd1, 0x8e, 0x5e, 0xff, 0xf7, 0x64, 0x6f, 0x9c,
-                       0x87, 0xdb, 0x4f, 0xf5, 0xe9, 0x8d, 0xf8, 0xf5,
-                       0x3d, 0x57, 0xb7, 0xa8, 0x13, 0x27, 0x1a, 0x48,
-                       0x8f, 0xd8, 0x4e, 0x9e, 0x4e, 0xca, 0xe9, 0x26,
-                       0x36, 0xab, 0x83, 0x1b, 0xd1, 0x7c, 0xd7, 0xe6,
-                       0xc8, 0x79, 0xd0, 0x4e, 0x8a, 0x91, 0xb5, 0x55,
-                       0x70, 0xa9, 0x4a, 0x6f, 0xef, 0x9e, 0xcf, 0x3c,
-                       0x70, 0x20, 0x7f, 0x69, 0xa4, 0xa8, 0x38, 0x7a,
-               }
-       )
-
-       // compute keypair from seed
-       prv := ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(D))
-       pub := prv.Public()
-       if bytes.Compare(pub.Bytes(), PUB) != 0 {
-               t.Fatal("Wrong public key")
-       }
-       ego := util.EncodeBinaryToString(pub.Bytes())
-       if ID != ego {
-               t.Logf("EGO = %s\n", ego)
-               t.Fatal("Wrong ego ID")
-       }
-
-       hBuf := make([]byte, 32)
-       hFull := DeriveH(pub, LABEL, CONTEXT)
-       h := hFull.Mod(ED25519_N)
-       util.CopyBlock(hBuf, h.Bytes())
-       if bytes.Compare(hBuf, H) != 0 {
-               if testing.Verbose() {
-                       t.Logf("H(computed) = %s\n", hex.EncodeToString(hBuf))
-                       t.Logf("H(expected) = %s\n", hex.EncodeToString(H))
-               }
-               t.Fatal("H mismatch")
-       }
-
-       dpub := pub.Mult(h)
-       dpub2 := DerivePublicKey(pub, LABEL, CONTEXT)
-       if !dpub.Q.Equals(dpub2.Q) {
-               t.Fatal("Q mismatch")
-       }
-
-       q := dpub.Q.Bytes()
-       if bytes.Compare(q, Q) != 0 {
-               if testing.Verbose() {
-                       t.Logf("derived_x(computed) = %s\n", 
hex.EncodeToString(q))
-                       t.Logf("derived_x(expected) = %s\n", 
hex.EncodeToString(Q))
-               }
-               t.Fatal("x-coordinate mismatch")
-       }
-       pk1 := dpub.Bytes()
-       pk2 := DerivePublicKey(pub, LABEL, CONTEXT).Bytes()
-       if bytes.Compare(pk1, pk2) != 0 {
-               if testing.Verbose() {
-                       t.Logf("derived(1) = %s\n", hex.EncodeToString(pk1))
-                       t.Logf("derived(2) = %s\n", hex.EncodeToString(pk2))
-               }
-               t.Fatal("Derived key mismatch")
-       }
-
-       out := sha512.Sum512(pk1)
-       if bytes.Compare(out[:], QUERY) != 0 {
-               if testing.Verbose() {
-                       t.Log("query(expected) = " + hex.EncodeToString(QUERY))
-                       t.Log("query(computed) = " + hex.EncodeToString(out[:]))
-               }
-               t.Fatal("Query mismatch")
-       }
-}
-
-func TestHKDF_gnunet(t *testing.T) {
-
-       var (
-               // SALT as defined in GNUnet
-               salt = []byte("key-derivation")
-               // expected PRK (as dumped in GNUnet)
-               PRK = []byte{
-                       0xEB, 0xFE, 0x63, 0xBA, 0x68, 0x2D, 0xA5, 0x5C,
-                       0xF8, 0x37, 0xCE, 0x8F, 0x94, 0x3B, 0x01, 0x44,
-                       0x1B, 0xF9, 0x67, 0x3D, 0xFC, 0x91, 0xED, 0x61,
-                       0x79, 0x94, 0xE8, 0x2A, 0x62, 0x0A, 0xE8, 0x6E,
-                       0x59, 0xDB, 0x56, 0x63, 0x80, 0x94, 0x63, 0xAC,
-                       0x8D, 0x35, 0xE2, 0xEA, 0xBA, 0xE6, 0xF3, 0xE8,
-                       0xC1, 0x4B, 0xC9, 0x4F, 0xBD, 0xE3, 0xE6, 0x61,
-                       0x01, 0xB3, 0xB2, 0x1C, 0x6F, 0x19, 0x73, 0x8B,
-               }
-               info = []byte("master-homegns")
-               // expected result (as dumped in GNUnet)
-               OKM = []byte{
-                       0x30, 0x86, 0x34, 0x7F, 0x2E, 0x12, 0xD7, 0x65,
-                       0x35, 0x70, 0x44, 0xE2, 0xF6, 0x9B, 0x84, 0x59,
-                       0x6E, 0xE1, 0x7F, 0x62, 0x93, 0xAD, 0xAE, 0x56,
-                       0x50, 0x6A, 0xA6, 0xD6, 0x8D, 0x39, 0x39, 0x95,
-               }
-       )
-       prk := hkdf.Extract(sha512.New, pub.Bytes(), salt)
-       if testing.Verbose() {
-               t.Log("PRK(computed) = " + hex.EncodeToString(prk))
-       }
-       if bytes.Compare(prk, PRK) != 0 {
-               t.Log("PRK(expected) = " + hex.EncodeToString(PRK))
-               t.Fatal("PRK mismatch")
-       }
-
-       rdr := hkdf.Expand(sha256.New, prk, info)
-       okm := make([]byte, len(OKM))
-       rdr.Read(okm)
-       if testing.Verbose() {
-               t.Log("OKM(computed) = " + hex.EncodeToString(okm))
-       }
-       if bytes.Compare(okm, OKM) != 0 {
-               t.Log("OKM(expected) = " + hex.EncodeToString(OKM))
-               t.Fatal("OKM mismatch")
-       }
-}
-
-func TestHDKF(t *testing.T) {
-       var (
-               ikm = []byte{
-                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
-                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
-                       0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
-               }
-               salt = []byte{
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-                       0x08, 0x09, 0x0a, 0x0b, 0x0c,
-               }
-               PRK = []byte{
-                       0x66, 0x57, 0x99, 0x82, 0x37, 0x37, 0xde, 0xd0,
-                       0x4a, 0x88, 0xe4, 0x7e, 0x54, 0xa5, 0x89, 0x0b,
-                       0xb2, 0xc3, 0xd2, 0x47, 0xc7, 0xa4, 0x25, 0x4a,
-                       0x8e, 0x61, 0x35, 0x07, 0x23, 0x59, 0x0a, 0x26,
-                       0xc3, 0x62, 0x38, 0x12, 0x7d, 0x86, 0x61, 0xb8,
-                       0x8c, 0xf8, 0x0e, 0xf8, 0x02, 0xd5, 0x7e, 0x2f,
-                       0x7c, 0xeb, 0xcf, 0x1e, 0x00, 0xe0, 0x83, 0x84,
-                       0x8b, 0xe1, 0x99, 0x29, 0xc6, 0x1b, 0x42, 0x37,
-               }
-               info = []byte{
-                       0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 
0xf9,
-               }
-               OKM = []byte{
-                       0x83, 0x23, 0x90, 0x08, 0x6c, 0xda, 0x71, 0xfb,
-                       0x47, 0x62, 0x5b, 0xb5, 0xce, 0xb1, 0x68, 0xe4,
-                       0xc8, 0xe2, 0x6a, 0x1a, 0x16, 0xed, 0x34, 0xd9,
-                       0xfc, 0x7f, 0xe9, 0x2c, 0x14, 0x81, 0x57, 0x93,
-                       0x38, 0xda, 0x36, 0x2c, 0xb8, 0xd9, 0xf9, 0x25,
-                       0xd7, 0xcb,
-               }
-       )
-
-       prk := hkdf.Extract(sha512.New, ikm, salt)
-       if testing.Verbose() {
-               t.Log("PRK(computed) = " + hex.EncodeToString(prk))
-       }
-       if bytes.Compare(prk, PRK) != 0 {
-               t.Log("PRK(expected) = " + hex.EncodeToString(PRK))
-               t.Fatal("PRK mismatch")
-       }
-
-       rdr := hkdf.Expand(sha512.New, prk, info)
-       okm := make([]byte, len(OKM))
-       rdr.Read(okm)
-       if testing.Verbose() {
-               t.Log("OKM(computed) = " + hex.EncodeToString(okm))
-       }
-       if bytes.Compare(okm, OKM) != 0 {
-               t.Log("OKM(expected) = " + hex.EncodeToString(OKM))
-               t.Fatal("OKM mismatch")
-       }
-}
diff --git a/src/gnunet/crypto/key_exchange_test.go 
b/src/gnunet/crypto/key_exchange_test.go
index 3696cae..a433ec3 100644
--- a/src/gnunet/crypto/key_exchange_test.go
+++ b/src/gnunet/crypto/key_exchange_test.go
@@ -57,6 +57,8 @@ var (
        prv_1, prv_2 *ed25519.PrivateKey
        pub_1, pub_2 *ed25519.PublicKey
        ss_1, ss_2   []byte
+
+       ED25519_N = ed25519.GetCurve().N
 )
 
 func calcSharedSecret() bool {
@@ -67,7 +69,7 @@ func calcSharedSecret() bool {
        // compute shared secret
        ss_1 = calc(prv_1, pub_2)
        ss_2 = calc(prv_2, pub_1)
-       return bytes.Compare(ss_1, ss_2) == 0
+       return bytes.Equal(ss_1, ss_2)
 }
 
 func TestDHE(t *testing.T) {
@@ -85,12 +87,11 @@ func TestDHE(t *testing.T) {
                t.Logf("SS_2 = %s\n", hex.EncodeToString(ss_2))
        }
 
-       if bytes.Compare(ss_1[:], ss) != 0 {
+       if !bytes.Equal(ss_1[:], ss) {
                t.Logf("SS(expected) = %s\n", hex.EncodeToString(ss))
                t.Logf("SS(computed) = %s\n", hex.EncodeToString(ss_1[:]))
                t.Fatal("Wrong shared secret:")
        }
-
 }
 
 func TestDHERandom(t *testing.T) {
diff --git a/src/gnunet/crypto/symmetric.go b/src/gnunet/crypto/symmetric.go
deleted file mode 100644
index c217c6d..0000000
--- a/src/gnunet/crypto/symmetric.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 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 crypto
-
-import (
-       "crypto/aes"
-       "crypto/cipher"
-       "crypto/rand"
-
-       "golang.org/x/crypto/twofish"
-)
-
-// Symmetric encryption in GNUnet uses a two-layer scheme:
-// * Encryption: OUT = twofish_cfb(aes_cfb(IN))
-// * Decryption: OUT = aes_cfb(twofish_cfb(IN))
-
-// SymmetricKey is a key for the GNUnet-specific two-layer encryption scheme.
-type SymmetricKey struct {
-       AESKey     []byte `size:"32"` // key for AES-CFB
-       TwofishKey []byte `size:"32"` // key for Twofish-CFB
-}
-
-// NewSymmetricKey generates a new (random) symmetric key.
-func NewSymmetricKey() *SymmetricKey {
-       skey := &SymmetricKey{
-               AESKey:     make([]byte, 32),
-               TwofishKey: make([]byte, 32),
-       }
-       rand.Read(skey.AESKey)
-       rand.Read(skey.TwofishKey)
-       return skey
-}
-
-// SymmetricIV is an initialization vector for the GNUnet-specific two-layer
-// encryption scheme.
-type SymmetricIV struct {
-       AESIv     []byte `size:"16"` // IV for AES-CFB
-       TwofishIv []byte `size:"16"` // IV for Twofish-CFB
-}
-
-// NewSymmetricIV generates a new (random) initialization vector.
-func NewSymmetricIV() *SymmetricIV {
-       iv := &SymmetricIV{
-               AESIv:     make([]byte, 16),
-               TwofishIv: make([]byte, 16),
-       }
-       rand.Read(iv.AESIv)
-       rand.Read(iv.TwofishIv)
-       return iv
-}
-
-// SymmetricDecrypt decrypts the data with given key and initialization vector.
-func SymmetricDecrypt(data []byte, skey *SymmetricKey, iv *SymmetricIV) 
([]byte, error) {
-       // Decrypt with Twofish CFB stream cipher
-       tf, err := twofish.NewCipher(skey.TwofishKey)
-       if err != nil {
-               return nil, err
-       }
-       stream := cipher.NewCFBDecrypter(tf, iv.TwofishIv)
-       out := make([]byte, len(data))
-       stream.XORKeyStream(out, data)
-
-       // Decrypt with AES CFB stream cipher
-       aes, err := aes.NewCipher(skey.AESKey)
-       if err != nil {
-               return nil, err
-       }
-       stream = cipher.NewCFBDecrypter(aes, iv.AESIv)
-       stream.XORKeyStream(out, out)
-       return out, nil
-}
-
-// SymmetricEncrypt encrypts the data with given key and initialization vector.
-func SymmetricEncrypt(data []byte, skey *SymmetricKey, iv *SymmetricIV) 
([]byte, error) {
-       // Encrypt with AES CFB stream cipher
-       aes, err := aes.NewCipher(skey.AESKey)
-       if err != nil {
-               return nil, err
-       }
-       stream := cipher.NewCFBEncrypter(aes, iv.AESIv)
-       out := make([]byte, len(data))
-       stream.XORKeyStream(out, data)
-
-       // Encrypt with Twofish CFB stream cipher
-       tf, err := twofish.NewCipher(skey.TwofishKey)
-       if err != nil {
-               return nil, err
-       }
-       stream = cipher.NewCFBEncrypter(tf, iv.TwofishIv)
-       stream.XORKeyStream(out, out)
-       return out, nil
-}
diff --git a/src/gnunet/enums/gns.go b/src/gnunet/enums/gns.go
index 6ba6f65..af9bbc7 100644
--- a/src/gnunet/enums/gns.go
+++ b/src/gnunet/enums/gns.go
@@ -85,6 +85,7 @@ var (
        GNS_TYPE_RECLAIM_MASTER        = 65551 // Record type for RECLAIM master
        GNS_TYPE_RECLAIM_OIDC_CLIENT   = 65552 // Record type for reclaim OIDC 
clients
        GNS_TYPE_RECLAIM_OIDC_REDIRECT = 65553 // Record type for reclaim OIDC 
redirect URIs
+       GNS_TYPE_EDKEY                 = 65556 // Record type for GNS zone 
transfer ("EDKEY").
 
        GNS_TYPE = map[int]string{
                GNS_TYPE_ANY:                   "GNS_TYPE_ANY",
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index 443666f..8527379 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -1,9 +1,21 @@
 module gnunet
 
-go 1.12
+go 1.17
 
 require (
-       github.com/bfix/gospel v0.0.0-20200326093412-d1b2381f0c46
+       github.com/bfix/gospel v1.2.10
+       github.com/go-redis/redis/v8 v8.5.0
+       github.com/go-sql-driver/mysql v1.5.0
+       github.com/gorilla/mux v1.8.0
+       github.com/mattn/go-sqlite3 v1.14.6
        github.com/miekg/dns v1.1.26
-       golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
+       golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
+)
+
+require (
+       github.com/cespare/xxhash/v2 v2.1.1 // indirect
+       github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // 
indirect
+       go.opentelemetry.io/otel v0.16.0 // indirect
+       golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+       golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
 )
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index 8c956b4..4fa501c 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,30 +1,123 @@
-github.com/bfix/gospel v0.0.0-20190922182041-6fcd6d4fd449 
h1:oIq3s14sMh1sq791v9VpR+GJvhVGbvuOWlfTjruRTDQ=
-github.com/bfix/gospel v0.0.0-20190922182041-6fcd6d4fd449/go.mod 
h1:RQYETFV9SP+VriIsHVqCntRpSbbRvCBnNTtbUl9NAKA=
-github.com/bfix/gospel v0.0.0-20200326093412-d1b2381f0c46 
h1:5aNd1/ISbO1ltgmyUGza7kdaN4fD/Qal6uKZk9goMhw=
-github.com/bfix/gospel v0.0.0-20200326093412-d1b2381f0c46/go.mod 
h1:RQYETFV9SP+VriIsHVqCntRpSbbRvCBnNTtbUl9NAKA=
+github.com/bfix/gospel v1.2.10 h1:a8l/sET2y+FVKIO5M1l5hdTlqLxstvkhp+b6FpAkxOU=
+github.com/bfix/gospel v1.2.10/go.mod 
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/cespare/xxhash/v2 v2.1.1 
h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0 
h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f 
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod 
h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/fsnotify/fsnotify v1.4.7/go.mod 
h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 
h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod 
h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-redis/redis/v8 v8.5.0 
h1:L3r1Q3I5WOUdXZGCP6g44EruKh0u3n6co5Hl5xWkdGA=
+github.com/go-redis/redis/v8 v8.5.0/go.mod 
h1:YmEcgBDttjnkbMzDAhDtQxY9yVA7jMN6PCR5HeMvqFE=
+github.com/go-sql-driver/mysql v1.5.0 
h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod 
h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang/protobuf v1.2.0/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod 
h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod 
h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod 
h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod 
h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod 
h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod 
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.3.0/go.mod 
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod 
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
+github.com/google/go-cmp v0.5.4/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod 
h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/hpcloud/tail v1.0.0/go.mod 
h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+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=
+github.com/mattn/go-sqlite3 v1.14.6 
h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod 
h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
 github.com/miekg/dns v1.1.26/go.mod 
h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod 
h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod 
h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod 
h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/gomega v1.7.1/go.mod 
h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod 
h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod 
h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 
h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.2.1/go.mod 
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v0.16.0 
h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw=
+go.opentelemetry.io/otel v0.16.0/go.mod 
h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 
h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
-golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 
h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod 
h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
-golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 
h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
-golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod 
h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed 
h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
+golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod 
h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod 
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod 
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478 
h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 
h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod 
h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod 
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod 
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 
h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 
h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe 
h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 
h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod 
h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod 
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod 
h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod 
h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod 
h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod 
h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod 
h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod 
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod 
h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 
h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod 
h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c 
h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/src/gnunet/message/const.go b/src/gnunet/message/const.go
index b706478..b07d211 100644
--- a/src/gnunet/message/const.go
+++ b/src/gnunet/message/const.go
@@ -29,27 +29,27 @@ var (
        // external addresses change an adversary cannot replay them 
indefinitely.
        // OTOH, we don't want to spend too much time generating PONG 
signatures,
        // so they must have some lifetime to reduce our CPU usage.
-       PONG_SIGNATURE_LIFETIME = 1 * time.Hour
+       PongSignatureLifetime = 1 * time.Hour
 
        // After how long do we expire an address in a HELLO that we just
        // validated?  This value is also used for our own addresses when we
        // create a HELLO.
-       HELLO_ADDRESS_EXPIRATION = 12 * time.Hour
+       HelloAddressExpiration = 12 * time.Hour
 
        // How often do we allow PINGing an address that we have not yet
        // validated?  This also determines how long we track an address that
        // we cannot validate (because after this time we can destroy the
        // validation record).
-       UNVALIDATED_PING_KEEPALIVE = 5 * time.Minute
+       UnvalidatedPingKeepAlive = 5 * time.Minute
 
        // How often do we PING an address that we have successfully validated
        // in the past but are not actively using?  Should be (significantly)
        // smaller than HELLO_ADDRESS_EXPIRATION.
-       VALIDATED_PING_FREQUENCY = 15 * time.Minute
+       ValidatedPingFrequency = 15 * time.Minute
 
        // How often do we PING an address that we are currently using?
-       CONNECTED_PING_FREQUENCY = 2 * time.Minute
+       ConnectedPingFrequency = 2 * time.Minute
 
        // How much delay is acceptable for sending the PING or PONG?
-       ACCEPTABLE_PING_DELAY = 1 * time.Second
+       AcceptablePingDelay = 1 * time.Second
 )
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 68a24d5..8cbf9ce 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -19,7 +19,6 @@
 package message
 
 import (
-       "errors"
        "fmt"
 )
 
@@ -30,7 +29,7 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
        // Transport
        //------------------------------------------------------------------
        case TRANSPORT_TCP_WELCOME:
-               return NewTransportTcpWelcomeMsg(nil), nil
+               return NewTransportTCPWelcomeMsg(nil), nil
        case HELLO:
                return NewHelloMsg(nil), nil
        case TRANSPORT_SESSION_QUOTA:
@@ -92,9 +91,9 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
        case REVOCATION_QUERY_RESPONSE:
                return NewRevocationQueryResponseMsg(true), nil
        case REVOCATION_REVOKE:
-               return NewRevocationRevokeMsg(nil, nil), nil
+               return NewRevocationRevokeMsg(nil), nil
        case REVOCATION_REVOKE_RESPONSE:
                return NewRevocationRevokeResponseMsg(false), nil
        }
-       return nil, errors.New(fmt.Sprintf("Unknown message type %d", msgType))
+       return nil, fmt.Errorf("unknown message type %d", msgType)
 }
diff --git a/src/gnunet/message/message.go b/src/gnunet/message/message.go
index fb9b4b0..1b826d7 100644
--- a/src/gnunet/message/message.go
+++ b/src/gnunet/message/message.go
@@ -31,33 +31,33 @@ var (
 
 // Message is an interface for all GNUnet-specific messages.
 type Message interface {
-       Header() *MessageHeader
+       Header() *Header
 }
 
-// MessageHeader encapsulates the common part of all GNUnet messages (at the
+// Header encapsulates the common part of all GNUnet messages (at the
 // beginning of the data).
-type MessageHeader struct {
+type Header struct {
        MsgSize uint16 `order:"big"`
        MsgType uint16 `order:"big"`
 }
 
 // Size returns the total size of the message (header + body)
-func (mh *MessageHeader) Size() uint16 {
+func (mh *Header) Size() uint16 {
        return mh.MsgSize
 }
 
 // Type returns the message type (defines the layout of the body data)
-func (mh *MessageHeader) Type() uint16 {
+func (mh *Header) Type() uint16 {
        return mh.MsgType
 }
 
 // GetMsgHeader returns the header of a message from a byte array (as the
 // serialized form).
-func GetMsgHeader(b []byte) (mh *MessageHeader, err error) {
+func GetMsgHeader(b []byte) (mh *Header, err error) {
        if b == nil || len(b) < 4 {
                return nil, ErrMsgHeaderTooSmall
        }
-       mh = new(MessageHeader)
+       mh = new(Header)
        err = data.Unmarshal(mh, b)
        return
 }
diff --git a/src/gnunet/message/msg_core.go b/src/gnunet/message/msg_core.go
index d6d427b..90c8804 100644
--- a/src/gnunet/message/msg_core.go
+++ b/src/gnunet/message/msg_core.go
@@ -80,8 +80,8 @@ func (m *EphemeralKeyMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *EphemeralKeyMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *EphemeralKeyMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 // Public extracts the public key of an announcing peer.
@@ -105,9 +105,9 @@ func (m *EphemeralKeyMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
 
 // NewEphemeralKey creates a new ephemeral key signed by a long-term private
 // key and the corresponding GNUnet message to announce the new key.
-func NewEphemeralKey(peerId []byte, ltPrv *ed25519.PrivateKey) 
(*ed25519.PrivateKey, *EphemeralKeyMsg, error) {
+func NewEphemeralKey(peerID []byte, ltPrv *ed25519.PrivateKey) 
(*ed25519.PrivateKey, *EphemeralKeyMsg, error) {
        msg := NewEphemeralKeyMsg()
-       copy(msg.SignedBlock.PeerID.Key, peerId)
+       copy(msg.SignedBlock.PeerID.Key, peerID)
        seed := util.NewRndArray(32)
        prv := ed25519.NewPrivateKeyFromSeed(seed)
        copy(msg.SignedBlock.EphemeralKey, prv.Public().Bytes())
diff --git a/src/gnunet/message/msg_dht.go b/src/gnunet/message/msg_dht.go
index 62a233f..9b73557 100644
--- a/src/gnunet/message/msg_dht.go
+++ b/src/gnunet/message/msg_dht.go
@@ -31,7 +31,7 @@ import (
 // DHT_CLIENT_GET
 //----------------------------------------------------------------------
 
-// DHTClientGetMsg
+// DHTClientGetMsg is the message for getting values from the DHT
 type DHTClientGetMsg struct {
        MsgSize   uint16           `order:"big"` // total size of message
        MsgType   uint16           `order:"big"` // DHT_CLIENT_GET (143)
@@ -39,7 +39,7 @@ type DHTClientGetMsg struct {
        ReplLevel uint32           `order:"big"` // Replication level for this 
message
        Type      uint32           `order:"big"` // The type for the data for 
the GET request (BLOCK_TYPE_???)
        Key       *crypto.HashCode // The key to search for
-       Id        uint64           `order:"big"` // Unique ID identifying this 
request
+       ID        uint64           `order:"big"` // Unique ID identifying this 
request
        XQuery    []byte           `size:"*"`    // Optional xquery
 }
 
@@ -55,12 +55,12 @@ func NewDHTClientGetMsg(key *crypto.HashCode) 
*DHTClientGetMsg {
                ReplLevel: 1,
                Type:      uint32(enums.BLOCK_TYPE_ANY),
                Key:       key,
-               Id:        0,
+               ID:        0,
                XQuery:    nil,
        }
 }
 
-// Set a (new) XQuery in this message and return previous XQuery.
+// SetXQuery sets a (new) XQuery in this message and return previous XQuery.
 func (m *DHTClientGetMsg) SetXQuery(xq []byte) []byte {
        prev := m.XQuery
        m.MsgSize -= uint16(len(prev))
@@ -72,26 +72,26 @@ func (m *DHTClientGetMsg) SetXQuery(xq []byte) []byte {
 // String returns a human-readable representation of the message.
 func (m *DHTClientGetMsg) String() string {
        return 
fmt.Sprintf("DHTClientGetMsg{Id:%d,Type=%d,Options=%d,Repl=%d,Key=%s}",
-               m.Id, m.Type, m.Options, m.ReplLevel, 
hex.EncodeToString(m.Key.Bits))
+               m.ID, m.Type, m.Options, m.ReplLevel, 
hex.EncodeToString(m.Key.Bits))
 }
 
 // Header returns the message header in a separate instance.
-func (msg *DHTClientGetMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *DHTClientGetMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // DHT_CLIENT_RESULT
 //----------------------------------------------------------------------
 
-// DHTClientResultMsg
+// DHTClientResultMsg is a message for DHT results
 type DHTClientResultMsg struct {
        MsgSize    uint16            `order:"big"` // total size of message
        MsgType    uint16            `order:"big"` // DHT_CLIENT_RESULT (145)
        Type       uint32            `order:"big"` // The type for the data
        PutPathLen uint32            `order:"big"` // Number of peers recorded 
in outgoing path
        GetPathLen uint32            `order:"big"` // Number of peers recorded 
from storage location
-       Id         uint64            `order:"big"` // Unique ID of the matching 
GET request
+       ID         uint64            `order:"big"` // Unique ID of the matching 
GET request
        Expire     util.AbsoluteTime // Expiration time
        Key        *crypto.HashCode  // The key that was searched for
        PutPath    []*util.PeerID    `size:"PutPathLen"` // put path
@@ -110,7 +110,7 @@ func NewDHTClientResultMsg(key *crypto.HashCode) 
*DHTClientResultMsg {
                Type:       0,
                PutPathLen: 0,
                GetPathLen: 0,
-               Id:         0,
+               ID:         0,
                Expire:     *new(util.AbsoluteTime),
                Key:        key,
                Data:       make([]byte, 0),
@@ -119,24 +119,24 @@ func NewDHTClientResultMsg(key *crypto.HashCode) 
*DHTClientResultMsg {
 
 // String returns a human-readable representation of the message.
 func (m *DHTClientResultMsg) String() string {
-       return fmt.Sprintf("DHTClientResultMsg{id:%d,expire=%s}", m.Id, 
m.Expire)
+       return fmt.Sprintf("DHTClientResultMsg{id:%d,expire=%s}", m.ID, 
m.Expire)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *DHTClientResultMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *DHTClientResultMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // DHT_CLIENT_GET_STOP
 //----------------------------------------------------------------------
 
-// DHTClientGetStopMsg
+// DHTClientGetStopMsg stops a pending DHT operation
 type DHTClientGetStopMsg struct {
        MsgSize  uint16           `order:"big"` // total size of message
        MsgType  uint16           `order:"big"` // DHT_CLIENT_GET_STOP (144)
        Reserved uint32           `order:"big"` // Reserved (0)
-       Id       uint64           `order:"big"` // Unique ID identifying this 
request
+       ID       uint64           `order:"big"` // Unique ID identifying this 
request
        Key      *crypto.HashCode // The key to search for
 }
 
@@ -149,17 +149,17 @@ func NewDHTClientGetStopMsg(key *crypto.HashCode) 
*DHTClientGetStopMsg {
                MsgSize:  80,
                MsgType:  DHT_CLIENT_GET_STOP,
                Reserved: 0, // mandatory
-               Id:       0,
+               ID:       0,
                Key:      key,
        }
 }
 
 // String returns a human-readable representation of the message.
 func (m *DHTClientGetStopMsg) String() string {
-       return fmt.Sprintf("DHTClientGetStopMsg{Id:%d,Key=%s}", m.Id, 
hex.EncodeToString(m.Key.Bits))
+       return fmt.Sprintf("DHTClientGetStopMsg{Id:%d,Key=%s}", m.ID, 
hex.EncodeToString(m.Key.Bits))
 }
 
 // Header returns the message header in a separate instance.
-func (msg *DHTClientGetStopMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *DHTClientGetStopMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
diff --git a/src/gnunet/message/msg_gns.go b/src/gnunet/message/msg_gns.go
index fb84502..35630d6 100644
--- a/src/gnunet/message/msg_gns.go
+++ b/src/gnunet/message/msg_gns.go
@@ -25,11 +25,11 @@ import (
        "gnunet/enums"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
        "github.com/bfix/gospel/logger"
 )
 
+// Error messages
 var (
        ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
 )
@@ -38,25 +38,25 @@ var (
 // GNS_LOOKUP
 //----------------------------------------------------------------------
 
-// GNSLookupMsg
-type GNSLookupMsg struct {
-       MsgSize  uint16 `order:"big"` // total size of message
-       MsgType  uint16 `order:"big"` // GNS_LOOKUP (500)
-       Id       uint32 `order:"big"` // Unique identifier for this request 
(for key collisions).
-       Zone     []byte `size:"32"`   // Zone that is to be used for lookup
-       Options  uint16 `order:"big"` // Local options for where to look for 
results
-       Reserved uint16 `order:"big"` // Always 0
-       Type     uint32 `order:"big"` // the type of record to look up
-       Name     []byte `size:"*"`    // zero-terminated name to look up
+// LookupMsg is a request message for a GNS name lookup
+type LookupMsg struct {
+       MsgSize  uint16          `order:"big"` // total size of message
+       MsgType  uint16          `order:"big"` // GNS_LOOKUP (500)
+       ID       uint32          `order:"big"` // Unique identifier for this 
request (for key collisions).
+       Zone     *crypto.ZoneKey ``            // Zone that is to be used for 
lookup
+       Options  uint16          `order:"big"` // Local options for where to 
look for results
+       Reserved uint16          `order:"big"` // Always 0
+       Type     uint32          `order:"big"` // the type of record to look up
+       Name     []byte          `size:"*"`    // zero-terminated name to look 
up
 }
 
 // NewGNSLookupMsg creates a new default message.
-func NewGNSLookupMsg() *GNSLookupMsg {
-       return &GNSLookupMsg{
+func NewGNSLookupMsg() *LookupMsg {
+       return &LookupMsg{
                MsgSize:  48, // record size with no name
                MsgType:  GNS_LOOKUP,
-               Id:       0,
-               Zone:     make([]byte, 32),
+               ID:       0,
+               Zone:     nil,
                Options:  uint16(enums.GNS_LO_DEFAULT),
                Reserved: 0,
                Type:     uint32(enums.GNS_TYPE_ANY),
@@ -65,81 +65,105 @@ func NewGNSLookupMsg() *GNSLookupMsg {
 }
 
 // SetName appends the name to lookup to the message
-func (m *GNSLookupMsg) SetName(name string) {
+func (m *LookupMsg) SetName(name string) {
        m.Name = util.Clone(append([]byte(name), 0))
        m.MsgSize = uint16(48 + len(m.Name))
 }
 
 // GetName returns the name to lookup from the message
-func (m *GNSLookupMsg) GetName() string {
+func (m *LookupMsg) GetName() string {
        size := len(m.Name)
        if m.Name[size-1] != 0 {
                logger.Println(logger.WARN, "GNS_LOOKUP name not 
NULL-terminated")
        } else {
-               size -= 1
+               size--
        }
        return string(m.Name[:size])
 }
 
 // String returns a human-readable representation of the message.
-func (m *GNSLookupMsg) String() string {
+func (m *LookupMsg) String() string {
        return fmt.Sprintf(
                "GNSLookupMsg{Id=%d,Zone=%s,Options=%d,Type=%d,Name=%s}",
-               m.Id, util.EncodeBinaryToString(m.Zone),
-               m.Options, m.Type, m.GetName())
+               m.ID, m.Zone.ID(), m.Options, m.Type, m.GetName())
 }
 
 // Header returns the message header in a separate instance.
-func (msg *GNSLookupMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *LookupMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // GNS_LOOKUP_RESULT
 //----------------------------------------------------------------------
 
-// GNSRecordSet ist the GNUnet data structure for a list of resource records
+// RecordSet ist the GNUnet data structure for a list of resource records
 // in a GNSBlock. As part of GNUnet messages, the record set is padded so that
 // the binary size of (records||padding) is the smallest power of two.
-type GNSRecordSet struct {
-       Count   uint32               `order:"big"`  // number of resource 
records
-       Records []*GNSResourceRecord `size:"Count"` // list of resource records
-       Padding []byte               `size:"*"`     // padding
+type RecordSet struct {
+       Count   uint32            `order:"big"`  // number of resource records
+       Records []*ResourceRecord `size:"Count"` // list of resource records
+       Padding []byte            `size:"*"`     // padding
 }
 
-// NewGNSRecordSet returns an empty resource record set.
-func NewGNSRecordSet() *GNSRecordSet {
-       return &GNSRecordSet{
+// NewRecordSet returns an empty resource record set.
+func NewRecordSet() *RecordSet {
+       return &RecordSet{
                Count:   0,
-               Records: make([]*GNSResourceRecord, 0),
+               Records: make([]*ResourceRecord, 0),
                Padding: make([]byte, 0),
        }
 }
 
 // AddRecord to append a resource record to the set.
-func (rs *GNSRecordSet) AddRecord(rec *GNSResourceRecord) {
+func (rs *RecordSet) AddRecord(rec *ResourceRecord) {
        rs.Count++
        rs.Records = append(rs.Records, rec)
 }
 
-// SignedBlockData: signed and encrypted list of resource records stored
-// in a GNSRecordSet
+// SetPadding (re-)calculates and allocates the padding.
+func (rs *RecordSet) SetPadding() {
+       size := 0
+       for _, rr := range rs.Records {
+               size += int(rr.Size) + 20
+       }
+       n := 1
+       for n < size {
+               n <<= 1
+       }
+       rs.Padding = make([]byte, n-size)
+}
+
+// Expires returns the earliest expiration timestamp for the records.
+func (rs *RecordSet) Expires() util.AbsoluteTime {
+       var expires util.AbsoluteTime
+       for i, rr := range rs.Records {
+               if i == 0 {
+                       expires = rr.Expires
+               } else if rr.Expires.Compare(expires) < 0 {
+                       expires = rr.Expires
+               }
+       }
+       return expires
+}
+
+// SignedBlockData represents the signed and encrypted list of resource
+// records stored in a GNSRecordSet
 type SignedBlockData struct {
-       Purpose *crypto.SignaturePurpose // Size and purpose of signature (8 
bytes)
-       Expire  util.AbsoluteTime        // Expiration time of the block.
+       Purpose *crypto.SignaturePurpose ``         // Size and purpose of 
signature (8 bytes)
+       Expire  util.AbsoluteTime        ``         // Expiration time of the 
block.
        EncData []byte                   `size:"*"` // encrypted GNSRecordSet
 
        // transient data (not serialized)
        data []byte // decrypted GNSRecord set
 }
 
-// GNSBlock is the result of GNS lookups for a given label in a zone.
+// Block is the result of GNS lookups for a given label in a zone.
 // An encrypted and signed container for GNS resource records that represents
 // the "atomic" data structure associated with a GNS label in a given zone.
-type GNSBlock struct {
-       Signature  []byte `size:"64"` // Signature of the block.
-       DerivedKey []byte `size:"32"` // Derived key used for signing
-       Block      *SignedBlockData
+type Block struct {
+       DerivedKeySig *crypto.ZoneSignature // Derived key used for signing
+       Block         *SignedBlockData
 
        // transient data (not serialized)
        checked   bool // block integrity checked
@@ -148,19 +172,19 @@ type GNSBlock struct {
 }
 
 // String returns the human-readable representation of a GNSBlock
-func (b *GNSBlock) String() string {
+func (b *Block) String() string {
        return fmt.Sprintf("GNSBlock{Verified=%v,Decrypted=%v,data=[%d]}",
                b.verified, b.decrypted, len(b.Block.EncData))
 }
 
 // Records returns the list of resource records in a block.
-func (b *GNSBlock) Records() ([]*GNSResourceRecord, error) {
+func (b *Block) Records() ([]*ResourceRecord, error) {
        // check if block is decrypted
        if !b.decrypted {
                return nil, ErrBlockNotDecrypted
        }
        // parse block data into record set
-       rs := NewGNSRecordSet()
+       rs := NewRecordSet()
        if err := data.Unmarshal(rs, b.Block.data); err != nil {
                return nil, err
        }
@@ -168,48 +192,37 @@ func (b *GNSBlock) Records() ([]*GNSResourceRecord, 
error) {
 }
 
 // Verify the integrity of the block data from a signature.
-func (b *GNSBlock) Verify(zoneKey *ed25519.PublicKey, label string) (err 
error) {
+func (b *Block) Verify(zkey *crypto.ZoneKey, label string) (err error) {
        // Integrity check performed
        b.checked = true
 
        // verify derived key
-       dkey := ed25519.NewPublicKeyFromBytes(b.DerivedKey)
-       dkey2 := crypto.DerivePublicKey(zoneKey, label, "gns")
-       if !dkey.Q.Equals(dkey2.Q) {
-               return fmt.Errorf("Invalid signature key for GNS Block")
+       dkey := b.DerivedKeySig.ZoneKey
+       dkey2, _ := zkey.Derive(label, "gns")
+       if !dkey.Equal(dkey2) {
+               return fmt.Errorf("invalid signature key for GNS Block")
        }
        // verify signature
-       var (
-               sig *ed25519.EcSignature
-               buf []byte
-               ok  bool
-       )
-       if sig, err = ed25519.NewEcSignatureFromBytes(b.Signature); err != nil {
-               return
-       }
+       var buf []byte
        if buf, err = data.Marshal(b.Block); err != nil {
                return
        }
-       if ok, err = dkey.EcVerify(buf, sig); err == nil && !ok {
-               err = fmt.Errorf("Signature verification failed for GNS block")
-       }
-       b.verified = true
+       b.verified, err = b.DerivedKeySig.Verify(buf)
        return
 }
 
-// Decrypt block data with a key/iv combination derived from (PKEY,label)
-func (b *GNSBlock) Decrypt(zoneKey *ed25519.PublicKey, label string) (err 
error) {
+// Decrypt block data with a key derived from zone key and label.
+func (b *Block) Decrypt(zkey *crypto.ZoneKey, label string) (err error) {
        // decrypt payload
-       b.Block.data, err = crypto.DecryptBlock(b.Block.EncData, zoneKey, label)
+       b.Block.data, err = zkey.Decrypt(b.Block.EncData, label, b.Block.Expire)
        b.decrypted = true
        return
 }
 
-// NewGNSBlock instantiates an empty GNS block
-func NewGNSBlock() *GNSBlock {
-       return &GNSBlock{
-               Signature:  make([]byte, 64),
-               DerivedKey: make([]byte, 32),
+// NewBlock instantiates an empty GNS block
+func NewBlock() *Block {
+       return &Block{
+               DerivedKeySig: nil,
                Block: &SignedBlockData{
                        Purpose: new(crypto.SignaturePurpose),
                        Expire:  *new(util.AbsoluteTime),
@@ -222,9 +235,9 @@ func NewGNSBlock() *GNSBlock {
        }
 }
 
-// GNSResourceRecord is the GNUnet-specific representation of resource
+// ResourceRecord is the GNUnet-specific representation of resource
 // records (not to be confused with DNS resource records).
-type GNSResourceRecord struct {
+type ResourceRecord struct {
        Expires util.AbsoluteTime // Expiration time for the record
        Size    uint32            `order:"big"` // Number of bytes in 'Data'
        Type    uint32            `order:"big"` // Type of the GNS/DNS record
@@ -233,33 +246,33 @@ type GNSResourceRecord struct {
 }
 
 // String returns a human-readable representation of the message.
-func (r *GNSResourceRecord) String() string {
+func (r *ResourceRecord) String() string {
        return 
fmt.Sprintf("GNSResourceRecord{type=%s,expire=%s,flags=%d,size=%d}",
                enums.GNS_TYPE[int(r.Type)], r.Expires, r.Flags, r.Size)
 }
 
-// GNSLookupResultMsg
-type GNSLookupResultMsg struct {
-       MsgSize uint16               `order:"big"`  // total size of message
-       MsgType uint16               `order:"big"`  // GNS_LOOKUP_RESULT (501)
-       Id      uint32               `order:"big"`  // Unique identifier for 
this request (for key collisions).
-       Count   uint32               `order:"big"`  // The number of records 
contained in response
-       Records []*GNSResourceRecord `size:"Count"` // GNS resource records
+// LookupResultMsg is a response message for a GNS name lookup request
+type LookupResultMsg struct {
+       MsgSize uint16            `order:"big"`  // total size of message
+       MsgType uint16            `order:"big"`  // GNS_LOOKUP_RESULT (501)
+       ID      uint32            `order:"big"`  // Unique identifier for this 
request (for key collisions).
+       Count   uint32            `order:"big"`  // The number of records 
contained in response
+       Records []*ResourceRecord `size:"Count"` // GNS resource records
 }
 
-// NewGNSLookupResultMsg
-func NewGNSLookupResultMsg(id uint32) *GNSLookupResultMsg {
-       return &GNSLookupResultMsg{
+// NewGNSLookupResultMsg returns a new lookup result message
+func NewGNSLookupResultMsg(id uint32) *LookupResultMsg {
+       return &LookupResultMsg{
                MsgSize: 12, // Empty result (no records)
                MsgType: GNS_LOOKUP_RESULT,
-               Id:      id,
+               ID:      id,
                Count:   0,
-               Records: make([]*GNSResourceRecord, 0),
+               Records: make([]*ResourceRecord, 0),
        }
 }
 
 // AddRecord adds a GNS resource recordto the response message.
-func (m *GNSLookupResultMsg) AddRecord(rec *GNSResourceRecord) error {
+func (m *LookupResultMsg) AddRecord(rec *ResourceRecord) error {
        recSize := 20 + int(rec.Size)
        if int(m.MsgSize)+recSize > enums.GNS_MAX_BLOCK_SIZE {
                return fmt.Errorf("gns.AddRecord(): MAX_BLOCK_SIZE reached")
@@ -271,11 +284,11 @@ func (m *GNSLookupResultMsg) AddRecord(rec 
*GNSResourceRecord) error {
 }
 
 // String returns a human-readable representation of the message.
-func (m *GNSLookupResultMsg) String() string {
-       return fmt.Sprintf("GNSLookupResultMsg{Id=%d,Count=%d}", m.Id, m.Count)
+func (m *LookupResultMsg) String() string {
+       return fmt.Sprintf("GNSLookupResultMsg{Id=%d,Count=%d}", m.ID, m.Count)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *GNSLookupResultMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *LookupResultMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
diff --git a/src/gnunet/message/msg_gns_test.go 
b/src/gnunet/message/msg_gns_test.go
new file mode 100644
index 0000000..ff985c3
--- /dev/null
+++ b/src/gnunet/message/msg_gns_test.go
@@ -0,0 +1,353 @@
+// 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"
+       "encoding/hex"
+       "gnunet/crypto"
+       "gnunet/enums"
+       "gnunet/util"
+       "testing"
+
+       "github.com/bfix/gospel/data"
+)
+
+// TestRecordsetPKEY implements the test case as defined in the GNS draft
+// (see section 13. Test vectors, case "PKEY")
+func TestRecordsetPKEY(t *testing.T) {
+       var (
+               D = []byte{
+                       // PKEY private scalar
+                       0x50, 0xd7, 0xb6, 0x52, 0xa4, 0xef, 0xea, 0xdf,
+                       0xf3, 0x73, 0x96, 0x90, 0x97, 0x85, 0xe5, 0x95,
+                       0x21, 0x71, 0xa0, 0x21, 0x78, 0xc8, 0xe7, 0xd4,
+                       0x50, 0xfa, 0x90, 0x79, 0x25, 0xfa, 0xfd, 0x98,
+               }
+               ZKEY = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x00,
+                       // PKEY public key
+                       0x67, 0x7c, 0x47, 0x7d, 0x2d, 0x93, 0x09, 0x7c,
+                       0x85, 0xb1, 0x95, 0xc6, 0xf9, 0x6d, 0x84, 0xff,
+                       0x61, 0xf5, 0x98, 0x2c, 0x2c, 0x4f, 0xe0, 0x2d,
+                       0x5a, 0x11, 0xfe, 0xdf, 0xb0, 0xc2, 0x90, 0x1f,
+               }
+               ZID    = 
"000G0037FH3QTBCK15Y8BCCNRVWPV17ZC7TSGB1C9ZG2TPGHZVFV1GMG3W"
+               RECSET = &RecordSet{
+                       Count: 2,
+                       Records: []*ResourceRecord{
+                               {
+                                       Expires: util.AbsoluteTime{
+                                               Val: uint64(14888744139323793),
+                                       },
+                                       Size:  4,
+                                       Type:  1,
+                                       Flags: 0,
+                                       Data: []byte{
+                                               0x01, 0x02, 0x03, 0x04,
+                                       },
+                               },
+                               {
+                                       Expires: util.AbsoluteTime{
+                                               Val: uint64(26147096139323793),
+                                       },
+                                       Size:  36,
+                                       Type:  crypto.ZONE_PKEY,
+                                       Flags: 2,
+                                       Data: []byte{
+                                               0x00, 0x01, 0x00, 0x00,
+                                               0x0e, 0x60, 0x1b, 0xe4, 0x2e, 
0xb5, 0x7f, 0xb4,
+                                               0x69, 0x76, 0x10, 0xcf, 0x3a, 
0x3b, 0x18, 0x34,
+                                               0x7b, 0x65, 0xa3, 0x3f, 0x02, 
0x5b, 0x5b, 0x17,
+                                               0x4a, 0xbe, 0xfb, 0x30, 0x80, 
0x7b, 0xfe, 0xcf,
+                                       },
+                               },
+                       },
+                       Padding: make([]byte, 0),
+               }
+               RDATA = []byte{
+                       0x00, 0x00, 0x00, 0x02, 0x00, 0x34, 0xe5, 0x3b,
+                       0xe1, 0x93, 0x79, 0x91, 0x00, 0x00, 0x00, 0x04,
+                       0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                       0x01, 0x02, 0x03, 0x04, 0x00, 0x5c, 0xe4, 0xa5,
+                       0x39, 0x4a, 0xd9, 0x91, 0x00, 0x00, 0x00, 0x24,
+                       0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                       0x00, 0x01, 0x00, 0x00, 0x0e, 0x60, 0x1b, 0xe4,
+                       0x2e, 0xb5, 0x7f, 0xb4, 0x69, 0x76, 0x10, 0xcf,
+                       0x3a, 0x3b, 0x18, 0x34, 0x7b, 0x65, 0xa3, 0x3f,
+                       0x02, 0x5b, 0x5b, 0x17, 0x4a, 0xbe, 0xfb, 0x30,
+                       0x80, 0x7b, 0xfe, 0xcf, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00,
+               }
+               NONCE = []byte{
+                       0x67, 0xeb, 0xda, 0x27, 0x00, 0x34, 0xe5, 0x3b,
+                       0xe1, 0x93, 0x79, 0x91, 0x00, 0x00, 0x00, 0x01,
+               }
+               SKEY = []byte{
+                       0x55, 0x1f, 0x15, 0x7a, 0xcf, 0x2b, 0xf1, 0xd4,
+                       0xa9, 0x75, 0x03, 0x69, 0x99, 0xea, 0x7c, 0x82,
+                       0x86, 0xac, 0xb3, 0x18, 0xf1, 0x49, 0x3e, 0x63,
+                       0xb5, 0x00, 0x60, 0x3a, 0x9b, 0x02, 0xe3, 0xe4,
+               }
+               BDATA = []byte{
+                       0x00, 0xe4, 0x83, 0x7e, 0xb5, 0xd0, 0x4f, 0x92,
+                       0x90, 0x3d, 0xe4, 0xb5, 0x23, 0x4e, 0x8c, 0xca,
+                       0xc5, 0x73, 0x6c, 0x97, 0x93, 0x37, 0x9a, 0x59,
+                       0xc3, 0x33, 0x75, 0xfc, 0x89, 0x51, 0xac, 0xa2,
+                       0xeb, 0x7a, 0xad, 0x06, 0x7b, 0xf9, 0xaf, 0x60,
+                       0xbf, 0x26, 0x75, 0x86, 0x46, 0xa1, 0x7f, 0x5e,
+                       0x5c, 0x3b, 0x62, 0x15, 0xf9, 0x40, 0x79, 0x54,
+                       0x5b, 0x1c, 0x4d, 0x4f, 0x1b, 0x2e, 0xbb, 0x22,
+                       0xc2, 0xb4, 0xda, 0xd4, 0x41, 0x26, 0x81, 0x7b,
+                       0x6f, 0x00, 0x15, 0x30, 0xd4, 0x76, 0x40, 0x1d,
+                       0xd6, 0x7a, 0xc0, 0x14, 0x85, 0x54, 0xe8, 0x06,
+                       0x35, 0x3d, 0xa9, 0xe4, 0x29, 0x80, 0x79, 0xf3,
+                       0xe1, 0xb1, 0x69, 0x42, 0xc4, 0x8d, 0x90, 0xc4,
+                       0x36, 0x0c, 0x61, 0x23, 0x8c, 0x40, 0xd9, 0xd5,
+                       0x29, 0x11, 0xae, 0xa5, 0x2c, 0xc0, 0x03, 0x7a,
+                       0xc7, 0x16, 0x0b, 0xb3, 0xcf, 0x5b, 0x2f, 0x4a,
+                       0x72, 0x2f, 0xd9, 0x6b,
+               }
+               LABEL = "test"
+       )
+
+       // check zone key pair
+       prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, D)
+       if err != nil {
+               t.Fatal(err)
+       }
+       zk := prv.Public()
+       if !bytes.Equal(zk.Bytes(), ZKEY) {
+               t.Logf("pub = %s\n", hex.EncodeToString(zk.Bytes()))
+               t.Logf("   != %s\n", hex.EncodeToString(ZKEY))
+               t.Fatal("zone key mismatch")
+       }
+       zid := zk.ID()
+       if zid != ZID {
+               t.Logf("id = %s\n", zid)
+               t.Logf("ID = %s\n", ZID)
+               t.Fatal("Zone ID mismatch")
+       }
+
+       // assemble and check recordset
+       RECSET.SetPadding()
+       rdata, err := data.Marshal(RECSET)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(rdata, RDATA) {
+               t.Logf("rdata = %s\n", hex.EncodeToString(rdata))
+               t.Logf("RDATA = %s\n", hex.EncodeToString(RDATA))
+               t.Fatal("RDATA mismatch")
+       }
+
+       // check symmetric keys and nonce
+       expires := RECSET.Expires()
+       skey := zk.BlockKey(LABEL, expires)
+       if !bytes.Equal(skey[32:], NONCE) {
+               t.Logf("nonce = %s\n", hex.EncodeToString(skey[32:]))
+               t.Logf("NONCE = %s\n", hex.EncodeToString(NONCE))
+               t.Fatal("NONCE mismatch")
+       }
+       if !bytes.Equal(skey[:32], SKEY) {
+               t.Logf("skey = %s\n", hex.EncodeToString(skey[:32]))
+               t.Logf("SKEY = %s\n", hex.EncodeToString(SKEY))
+               t.Fatal("SKEY mismatch")
+       }
+
+       // check block encryption
+       bdata, err := zk.Encrypt(rdata, LABEL, expires)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(bdata, BDATA) {
+               t.Logf("bdata = %s\n", hex.EncodeToString(bdata))
+               t.Logf("BDATA = %s\n", hex.EncodeToString(BDATA))
+               t.Fatal("BDATA mismatch")
+       }
+}
+
+// TestRecordsetEDKEY implements the test case as defined in the GNS draft
+// (see section 13. Test vectors, case "EDKEY")
+func TestRecordsetEDKEY(t *testing.T) {
+       var (
+               SEED = []byte{
+                       // EDKEY private key (seed)
+                       0x5a, 0xf7, 0x02, 0x0e, 0xe1, 0x91, 0x60, 0x32,
+                       0x88, 0x32, 0x35, 0x2b, 0xbc, 0x6a, 0x68, 0xa8,
+                       0xd7, 0x1a, 0x7c, 0xbe, 0x1b, 0x92, 0x99, 0x69,
+                       0xa7, 0xc6, 0x6d, 0x41, 0x5a, 0x0d, 0x8f, 0x65,
+               }
+               ZKEY = []byte{
+                       // zone type
+                       0x00, 0x01, 0x00, 0x14,
+                       // EDKEY public key data
+                       0x3c, 0xf4, 0xb9, 0x24, 0x03, 0x20, 0x22, 0xf0,
+                       0xdc, 0x50, 0x58, 0x14, 0x53, 0xb8, 0x5d, 0x93,
+                       0xb0, 0x47, 0xb6, 0x3d, 0x44, 0x6c, 0x58, 0x45,
+                       0xcb, 0x48, 0x44, 0x5d, 0xdb, 0x96, 0x68, 0x8f,
+               }
+               ZID    = 
"000G051WYJWJ80S04BRDRM2R2H9VGQCKP13VCFA4DHC4BJT88HEXQ5K8HW"
+               RECSET = &RecordSet{
+                       Count: 2,
+                       Records: []*ResourceRecord{
+                               {
+                                       Expires: util.AbsoluteTime{
+                                               Val: uint64(2463385894000000),
+                                       },
+                                       Size:  4,
+                                       Type:  1,
+                                       Flags: 0,
+                                       Data: []byte{
+                                               0x01, 0x02, 0x03, 0x04,
+                                       },
+                               },
+                               {
+                                       Expires: util.AbsoluteTime{
+                                               Val: uint64(49556645701000000),
+                                       },
+                                       Size:  36,
+                                       Type:  uint32(enums.GNS_TYPE_NICK),
+                                       Flags: 2,
+                                       Data: []byte{
+                                               0x4d, 0x79, 0x20, 0x4e, 0x69, 
0x63, 0x6b, 0x00,
+                                               0x45, 0x6e, 0x63, 0x72, 0x79, 
0x70, 0x74, 0x69,
+                                               0x6f, 0x6e, 0x20, 0x4e, 0x4f, 
0x4e, 0x43, 0x45,
+                                               0x7c, 0x45, 0x58, 0x50, 0x49, 
0x52, 0x41, 0x54,
+                                               0x49, 0x4f, 0x4e, 0x3a,
+                                       },
+                               },
+                       },
+                       Padding: make([]byte, 0),
+               }
+               RDATA = []byte{
+                       0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0xc0, 0x6f,
+                       0xb9, 0x28, 0x15, 0x80, 0x00, 0x00, 0x00, 0x04,
+                       0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                       0x01, 0x02, 0x03, 0x04, 0x00, 0xb0, 0x0f, 0x81,
+                       0xb7, 0x44, 0x9b, 0x40, 0x00, 0x00, 0x00, 0x24,
+                       0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+                       0x4d, 0x79, 0x20, 0x4e, 0x69, 0x63, 0x6b, 0x00,
+                       0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69,
+                       0x6f, 0x6e, 0x20, 0x4e, 0x4f, 0x4e, 0x43, 0x45,
+                       0x7c, 0x45, 0x58, 0x50, 0x49, 0x52, 0x41, 0x54,
+                       0x49, 0x4f, 0x4e, 0x3a, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00,
+               }
+               NONCE = []byte{
+                       0x95, 0x4c, 0xb5, 0xd6, 0x31, 0x9f, 0x9e, 0x31,
+                       0xff, 0x80, 0x4a, 0xe6, 0x83, 0xbc, 0x19, 0x37,
+                       0x00, 0x08, 0xc0, 0x6f, 0xb9, 0x28, 0x15, 0x80,
+               }
+               SKEY = []byte{
+                       0x08, 0x34, 0xa3, 0xa3, 0xae, 0x09, 0xcb, 0x3b,
+                       0xd9, 0x8c, 0xec, 0xdb, 0x47, 0x7c, 0x3b, 0x32,
+                       0x45, 0xd0, 0xce, 0xda, 0x94, 0x8f, 0x9e, 0xbb,
+                       0xba, 0x3b, 0x17, 0x91, 0x61, 0x7b, 0xee, 0x69,
+               }
+               BDATA = []byte{
+                       0x36, 0x07, 0xf8, 0x62, 0xfc, 0xf4, 0xc6, 0xd4,
+                       0x86, 0x1c, 0x7a, 0x06, 0x08, 0x81, 0x28, 0xbb,
+                       0x3d, 0x6c, 0xca, 0xe2, 0xb1, 0x4e, 0xf4, 0x25,
+                       0xe3, 0xd6, 0xbb, 0xd6, 0x27, 0x1a, 0x71, 0xe5,
+                       0x42, 0x1c, 0x25, 0x1c, 0xfb, 0x5e, 0xb6, 0xd7,
+                       0xbc, 0x9e, 0x74, 0xb2, 0xe8, 0xc8, 0xd8, 0x6c,
+                       0xe0, 0x65, 0x37, 0x12, 0x0c, 0x2e, 0xe2, 0x28,
+                       0x5b, 0x93, 0xc5, 0xaf, 0xb7, 0x79, 0xf9, 0xcf,
+                       0x50, 0x2e, 0x16, 0xa5, 0xad, 0x30, 0xe6, 0x22,
+                       0xed, 0x58, 0x92, 0xd2, 0x46, 0xc0, 0x34, 0x11,
+                       0x70, 0xf0, 0xc5, 0x1c, 0x39, 0x40, 0xab, 0x33,
+                       0x47, 0xdc, 0x91, 0x56, 0x5f, 0x36, 0x6d, 0xb6,
+                       0x23, 0x56, 0x73, 0x9a, 0xd8, 0xde, 0x68, 0x21,
+                       0x12, 0x68, 0xf0, 0xc0, 0x44, 0x00, 0x81, 0xd8,
+                       0xaf, 0x8a, 0x6e, 0x16, 0x45, 0xa6, 0x92, 0x46,
+                       0xb4, 0x34, 0xe2, 0xc8, 0x76, 0x9f, 0x00, 0x1b,
+                       0xd5, 0x1a, 0xb3, 0x73, 0x5e, 0x02, 0xb4, 0x81,
+                       0xa6, 0x83, 0x0f, 0x00, 0xd2, 0xf6, 0xf3, 0x15,
+                       0xdf, 0x54, 0x20, 0x90,
+               }
+               LABEL = "test"
+       )
+
+       // check zone key pair
+       prv, err := crypto.NewZonePrivate(crypto.ZONE_EDKEY, SEED)
+       if err != nil {
+               t.Fatal(err)
+       }
+       zk := prv.Public()
+       if !bytes.Equal(zk.Bytes(), ZKEY) {
+               t.Logf("zkey = %s\n", hex.EncodeToString(zk.Bytes()))
+               t.Logf("    != %s\n", hex.EncodeToString(ZKEY))
+               t.Fatal("zone key mismatch")
+       }
+       zid := zk.ID()
+       if zid != ZID {
+               t.Logf("id = %s\n", zid)
+               t.Logf("ID = %s\n", ZID)
+               t.Fatal("Zone ID mismatch")
+       }
+
+       // assemble and check recordset
+       RECSET.SetPadding()
+       rdata, err := data.Marshal(RECSET)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(rdata, RDATA) {
+               t.Logf("rdata = %s\n", hex.EncodeToString(rdata))
+               t.Logf("RDATA = %s\n", hex.EncodeToString(RDATA))
+               t.Fatal("RDATA mismatch")
+       }
+
+       // check symmetric keys and nonce
+       expires := RECSET.Expires()
+       skey := zk.BlockKey(LABEL, expires)
+       if !bytes.Equal(skey[32:], NONCE) {
+               t.Logf("nonce = %s\n", hex.EncodeToString(skey[32:]))
+               t.Logf("NONCE = %s\n", hex.EncodeToString(NONCE))
+               t.Fatal("NONCE mismatch")
+       }
+       if !bytes.Equal(skey[:32], SKEY) {
+               t.Logf("skey = %s\n", hex.EncodeToString(skey[:32]))
+               t.Logf("SKEY = %s\n", hex.EncodeToString(SKEY))
+               t.Fatal("SKEY mismatch")
+       }
+
+       // check block encryption
+       bdata, err := zk.Encrypt(rdata, LABEL, expires)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(bdata, BDATA) {
+               t.Logf("bdata = %s\n", hex.EncodeToString(bdata))
+               t.Logf("BDATA = %s\n", hex.EncodeToString(BDATA))
+               t.Fatal("BDATA mismatch")
+       }
+}
diff --git a/src/gnunet/message/msg_namecache.go 
b/src/gnunet/message/msg_namecache.go
index 1f69eed..c3a0ac7 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -30,11 +30,11 @@ import (
 // NAMECACHE_LOOKUP_BLOCK
 //----------------------------------------------------------------------
 
-// NamecacheLookupMsg
+// NamecacheLookupMsg is request message for lookups in local namecache
 type NamecacheLookupMsg struct {
        MsgSize uint16           `order:"big"` // total size of message
        MsgType uint16           `order:"big"` // NAMECACHE_LOOKUP_BLOCK (431)
-       Id      uint32           `order:"big"` // Request Id
+       ID      uint32           `order:"big"` // Request Id
        Query   *crypto.HashCode // Query data
 }
 
@@ -46,7 +46,7 @@ func NewNamecacheLookupMsg(query *crypto.HashCode) 
*NamecacheLookupMsg {
        return &NamecacheLookupMsg{
                MsgSize: 72,
                MsgType: NAMECACHE_LOOKUP_BLOCK,
-               Id:      0,
+               ID:      0,
                Query:   query,
        }
 }
@@ -54,83 +54,78 @@ func NewNamecacheLookupMsg(query *crypto.HashCode) 
*NamecacheLookupMsg {
 // String returns a human-readable representation of the message.
 func (m *NamecacheLookupMsg) String() string {
        return fmt.Sprintf("NamecacheLookupMsg{Id=%d,Query=%s}",
-               m.Id, hex.EncodeToString(m.Query.Bits))
+               m.ID, hex.EncodeToString(m.Query.Bits))
 }
 
 // Header returns the message header in a separate instance.
-func (msg *NamecacheLookupMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *NamecacheLookupMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // NAMECACHE_LOOKUP_BLOCK_RESPONSE
 //----------------------------------------------------------------------
 
-// NamecacheLookupResultMsg
+// NamecacheLookupResultMsg is the response message for namecache lookups.
 type NamecacheLookupResultMsg struct {
-       MsgSize    uint16            `order:"big"` // total size of message
-       MsgType    uint16            `order:"big"` // 
NAMECACHE_LOOKUP_BLOCK_RESPONSE (432)
-       Id         uint32            `order:"big"` // Request Id
-       Expire     util.AbsoluteTime // Expiration time
-       Signature  []byte            `size:"64"` // ECDSA signature
-       DerivedKey []byte            `size:"32"` // Derived public key
-       EncData    []byte            `size:"*"`  // Encrypted block data
+       MsgSize       uint16                `order:"big"` // total size of 
message
+       MsgType       uint16                `order:"big"` // 
NAMECACHE_LOOKUP_BLOCK_RESPONSE (432)
+       ID            uint32                `order:"big"` // Request Id
+       Expire        util.AbsoluteTime     ``            // Expiration time
+       DerivedKeySig *crypto.ZoneSignature ``            // Derived public key
+       EncData       []byte                `size:"*"`    // Encrypted block 
data
 }
 
 // NewNamecacheLookupResultMsg creates a new default message.
 func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg {
        return &NamecacheLookupResultMsg{
-               MsgSize:    112,
-               MsgType:    NAMECACHE_LOOKUP_BLOCK_RESPONSE,
-               Id:         0,
-               Expire:     *new(util.AbsoluteTime),
-               Signature:  make([]byte, 64),
-               DerivedKey: make([]byte, 32),
-               EncData:    make([]byte, 0),
+               MsgSize:       112,
+               MsgType:       NAMECACHE_LOOKUP_BLOCK_RESPONSE,
+               ID:            0,
+               Expire:        *new(util.AbsoluteTime),
+               DerivedKeySig: nil,
+               EncData:       nil,
        }
 }
 
 // String returns a human-readable representation of the message.
 func (m *NamecacheLookupResultMsg) String() string {
        return fmt.Sprintf("NamecacheLookupResultMsg{id=%d,expire=%s}",
-               m.Id, m.Expire)
+               m.ID, m.Expire)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *NamecacheLookupResultMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *NamecacheLookupResultMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // NAMECACHE_CACHE_BLOCK
 //----------------------------------------------------------------------
 
-// NamecacheCacheMsg
+// NamecacheCacheMsg is the request message to put a name into the local cache.
 type NamecacheCacheMsg struct {
-       MsgSize    uint16            `order:"big"` // total size of message
-       MsgType    uint16            `order:"big"` // NAMECACHE_CACHE_BLOCK 
(433)
-       Id         uint32            `order:"big"` // Request Id
-       Expire     util.AbsoluteTime // Expiration time
-       Signature  []byte            `size:"64"` // ECDSA signature
-       DerivedKey []byte            `size:"32"` // Derived public key
-       EncData    []byte            `size:"*"`  // Encrypted block data
+       MsgSize       uint16                `order:"big"` // total size of 
message
+       MsgType       uint16                `order:"big"` // 
NAMECACHE_CACHE_BLOCK (433)
+       ID            uint32                `order:"big"` // Request Id
+       Expire        util.AbsoluteTime     ``            // Expiration time
+       DerivedKeySig *crypto.ZoneSignature ``            // Derived public key 
and signature
+       EncData       []byte                `size:"*"`    // Encrypted block 
data
 }
 
-// NewNamecacheLookupMsg creates a new default message.
-func NewNamecacheCacheMsg(block *GNSBlock) *NamecacheCacheMsg {
+// NewNamecacheCacheMsg creates a new default message.
+func NewNamecacheCacheMsg(block *Block) *NamecacheCacheMsg {
        msg := &NamecacheCacheMsg{
-               MsgSize:    108,
-               MsgType:    NAMECACHE_BLOCK_CACHE,
-               Id:         0,
-               Expire:     *new(util.AbsoluteTime),
-               Signature:  make([]byte, 64),
-               DerivedKey: make([]byte, 32),
-               EncData:    make([]byte, 0),
+               MsgSize:       108,
+               MsgType:       NAMECACHE_BLOCK_CACHE,
+               ID:            0,
+               Expire:        *new(util.AbsoluteTime),
+               DerivedKeySig: nil,
+               EncData:       make([]byte, 0),
        }
        if block != nil {
+               msg.DerivedKeySig = block.DerivedKeySig
                msg.Expire = block.Block.Expire
-               copy(msg.Signature, block.Signature)
-               copy(msg.DerivedKey, block.DerivedKey)
                size := len(block.Block.EncData)
                msg.EncData = make([]byte, size)
                copy(msg.EncData, block.Block.EncData)
@@ -142,23 +137,23 @@ func NewNamecacheCacheMsg(block *GNSBlock) 
*NamecacheCacheMsg {
 // String returns a human-readable representation of the message.
 func (m *NamecacheCacheMsg) String() string {
        return fmt.Sprintf("NewNamecacheCacheMsg{id=%d,expire=%s}",
-               m.Id, m.Expire)
+               m.ID, m.Expire)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *NamecacheCacheMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *NamecacheCacheMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // NAMECACHE_BLOCK_CACHE_RESPONSE
 //----------------------------------------------------------------------
 
-// NamecacheCacheResponseMsg
+// NamecacheCacheResponseMsg is the reponse 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)
-       Id      uint32 `order:"big"` // Request Id
+       ID      uint32 `order:"big"` // Request Id
        Result  int32  `order:"big"` // Result code
 }
 
@@ -167,7 +162,7 @@ func NewNamecacheCacheResponseMsg() 
*NamecacheCacheResponseMsg {
        return &NamecacheCacheResponseMsg{
                MsgSize: 12,
                MsgType: NAMECACHE_BLOCK_CACHE_RESPONSE,
-               Id:      0,
+               ID:      0,
                Result:  0,
        }
 }
@@ -175,10 +170,10 @@ func NewNamecacheCacheResponseMsg() 
*NamecacheCacheResponseMsg {
 // String returns a human-readable representation of the message.
 func (m *NamecacheCacheResponseMsg) String() string {
        return fmt.Sprintf("NamecacheCacheResponseMsg{id=%d,result=%d}",
-               m.Id, m.Result)
+               m.ID, m.Result)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *NamecacheCacheResponseMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *NamecacheCacheResponseMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
diff --git a/src/gnunet/message/msg_revocation.go 
b/src/gnunet/message/msg_revocation.go
index c17f440..3174c55 100644
--- a/src/gnunet/message/msg_revocation.go
+++ b/src/gnunet/message/msg_revocation.go
@@ -21,52 +21,47 @@ package message
 import (
        "fmt"
 
+       "gnunet/crypto"
        "gnunet/util"
-
-       "github.com/bfix/gospel/crypto/ed25519"
 )
 
 //----------------------------------------------------------------------
 // REVOCATION_QUERY
 //----------------------------------------------------------------------
 
-// RevocationQueryMsg
+// RevocationQueryMsg is a request message to check if a key is revoked
 type RevocationQueryMsg struct {
-       MsgSize  uint16 `order:"big"` // total size of message
-       MsgType  uint16 `order:"big"` // REVOCATION_QUERY (636)
-       Reserved uint32 `order:"big"` // Reserved for future use
-       Zone     []byte `size:"32"`   // Zone that is to be checked for 
revocation
+       MsgSize  uint16          `order:"big"` // total size of message
+       MsgType  uint16          `order:"big"` // REVOCATION_QUERY (636)
+       Reserved uint32          `order:"big"` // Reserved for future use
+       Zone     *crypto.ZoneKey // Zone that is to be checked for revocation
 }
 
 // NewRevocationQueryMsg creates a new message for a given zone.
-func NewRevocationQueryMsg(zone *ed25519.PublicKey) *RevocationQueryMsg {
-       msg := &RevocationQueryMsg{
+func NewRevocationQueryMsg(zkey *crypto.ZoneKey) *RevocationQueryMsg {
+       return &RevocationQueryMsg{
                MsgSize:  40,
                MsgType:  REVOCATION_QUERY,
                Reserved: 0,
-               Zone:     make([]byte, 32),
-       }
-       if zone != nil {
-               copy(msg.Zone, zone.Bytes())
+               Zone:     zkey,
        }
-       return msg
 }
 
 // String returns a human-readable representation of the message.
 func (m *RevocationQueryMsg) String() string {
-       return fmt.Sprintf("RevocationQueryMsg{zone=%s}", 
util.EncodeBinaryToString(m.Zone))
+       return fmt.Sprintf("RevocationQueryMsg{zone=%s}", m.Zone.ID())
 }
 
 // Header returns the message header in a separate instance.
-func (msg *RevocationQueryMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *RevocationQueryMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // REVOCATION_QUERY_RESPONSE
 //----------------------------------------------------------------------
 
-// RevocationQueryResponseMsg
+// RevocationQueryResponseMsg is a response message for revocation checks.
 type RevocationQueryResponseMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637)
@@ -92,60 +87,51 @@ func (m *RevocationQueryResponseMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *RevocationQueryResponseMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *RevocationQueryResponseMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // REVOCATION_REVOKE
 //----------------------------------------------------------------------
 
-// RevocationRevokeMsg
+// RevocationRevokeMsg is a request to revoke a given key with PoW data
 type RevocationRevokeMsg struct {
-       MsgSize   uint16            `order:"big"` // total size of message
-       MsgType   uint16            `order:"big"` // REVOCATION_REVOKE (638)
-       Timestamp util.AbsoluteTime // Timestamp of revocation creation
-       TTL       util.RelativeTime // TTL of revocation
-       PoWs      []uint64          `size:"32" order:"big"` // (Sorted) list of 
PoW values
-       Signature []byte            `size:"64"`             // Signature 
(Proof-of-ownership).
-       ZoneKey   []byte            `size:"32"`             // public zone key 
to be revoked
+       MsgSize    uint16                `order:"big"`           // total size 
of message
+       MsgType    uint16                `order:"big"`           // 
REVOCATION_REVOKE (638)
+       Timestamp  util.AbsoluteTime     ``                      // Timestamp 
of revocation creation
+       TTL        util.RelativeTime     ``                      // TTL of 
revocation
+       PoWs       []uint64              `size:"32" order:"big"` // (Sorted) 
list of PoW values
+       ZoneKeySig *crypto.ZoneSignature ``                      // public zone 
key (with signature) to be revoked
 }
 
 // NewRevocationRevokeMsg creates a new message for a given zone.
-func NewRevocationRevokeMsg(zoneKey *ed25519.PublicKey, sig 
*ed25519.EcSignature) *RevocationRevokeMsg {
-       msg := &RevocationRevokeMsg{
-               MsgSize:   364,
-               MsgType:   REVOCATION_REVOKE,
-               Timestamp: util.AbsoluteTimeNow(),
-               TTL:       util.RelativeTime{0},
-               PoWs:      make([]uint64, 32),
-               Signature: make([]byte, 64),
-               ZoneKey:   make([]byte, 32),
-       }
-       if zoneKey != nil {
-               copy(msg.ZoneKey, zoneKey.Bytes())
-       }
-       if sig != nil {
-               copy(msg.Signature, sig.Bytes())
+func NewRevocationRevokeMsg(zsig *crypto.ZoneSignature) *RevocationRevokeMsg {
+       return &RevocationRevokeMsg{
+               MsgSize:    364,
+               MsgType:    REVOCATION_REVOKE,
+               Timestamp:  util.AbsoluteTimeNow(),
+               TTL:        util.RelativeTime{},
+               PoWs:       make([]uint64, 32),
+               ZoneKeySig: zsig,
        }
-       return msg
 }
 
 // String returns a human-readable representation of the message.
 func (m *RevocationRevokeMsg) String() string {
-       return fmt.Sprintf("RevocationRevokeMsg{zone=%s}", 
util.EncodeBinaryToString(m.ZoneKey))
+       return fmt.Sprintf("RevocationRevokeMsg{zone=%s}", m.ZoneKeySig.ID())
 }
 
 // Header returns the message header in a separate instance.
-func (msg *RevocationRevokeMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *RevocationRevokeMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // REVOCATION_REVOKE_RESPONSE
 //----------------------------------------------------------------------
 
-// RevocationRevokeResponseMsg
+// RevocationRevokeResponseMsg is a response message for a revocation request
 type RevocationRevokeResponseMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // REVOCATION_REVOKE_RESPONSE (639)
@@ -171,6 +157,6 @@ func (m *RevocationRevokeResponseMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *RevocationRevokeResponseMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *RevocationRevokeResponseMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
diff --git a/src/gnunet/message/msg_transport.go 
b/src/gnunet/message/msg_transport.go
index b2e4346..6e64c4d 100644
--- a/src/gnunet/message/msg_transport.go
+++ b/src/gnunet/message/msg_transport.go
@@ -34,19 +34,19 @@ import (
 // TRANSPORT_TCP_WELCOME
 //----------------------------------------------------------------------
 
-// TransportTcpWelcomeMsg
-type TransportTcpWelcomeMsg struct {
+// TransportTCPWelcomeMsg is a welcome message for new TCP connections.
+type TransportTCPWelcomeMsg struct {
        MsgSize uint16       `order:"big"` // total size of message
        MsgType uint16       `order:"big"` // TRANSPORT_TCP_WELCOME (61)
        PeerID  *util.PeerID // Peer identity (EdDSA public key)
 }
 
-// NewTransportTcpWelcomeMsg creates a new message for a given peer.
-func NewTransportTcpWelcomeMsg(peerid *util.PeerID) *TransportTcpWelcomeMsg {
+// NewTransportTCPWelcomeMsg creates a new message for a given peer.
+func NewTransportTCPWelcomeMsg(peerid *util.PeerID) *TransportTCPWelcomeMsg {
        if peerid == nil {
                peerid = util.NewPeerID(nil)
        }
-       return &TransportTcpWelcomeMsg{
+       return &TransportTCPWelcomeMsg{
                MsgSize: 36,
                MsgType: TRANSPORT_TCP_WELCOME,
                PeerID:  peerid,
@@ -54,13 +54,13 @@ func NewTransportTcpWelcomeMsg(peerid *util.PeerID) 
*TransportTcpWelcomeMsg {
 }
 
 // String returns a human-readable representation of the message.
-func (m *TransportTcpWelcomeMsg) String() string {
+func (m *TransportTCPWelcomeMsg) String() string {
        return fmt.Sprintf("TransportTcpWelcomeMsg{peer=%s}", m.PeerID)
 }
 
 // Header returns the message header in a separate instance.
-func (msg *TransportTcpWelcomeMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *TransportTCPWelcomeMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
@@ -72,7 +72,7 @@ func (msg *TransportTcpWelcomeMsg) Header() *MessageHeader {
 // connection which the receiver (of the PING) initiated is still valid.
 //----------------------------------------------------------------------
 
-// TransportPingMsg
+// TransportPingMsg is a PING request message
 type TransportPingMsg struct {
        MsgSize   uint16       `order:"big"` // total size of message
        MsgType   uint16       `order:"big"` // TRANSPORT_PING (372)
@@ -81,7 +81,7 @@ type TransportPingMsg struct {
        Address   []byte       `size:"*"` // encoded address
 }
 
-// TransportPingMsg creates a new message for given peer with an address to
+// NewTransportPingMsg creates a new message for given peer with an address to
 // be validated.
 func NewTransportPingMsg(target *util.PeerID, a *util.Address) 
*TransportPingMsg {
        if target == nil {
@@ -112,8 +112,8 @@ func (m *TransportPingMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *TransportPingMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *TransportPingMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
@@ -155,7 +155,7 @@ func NewSignedAddress(a *util.Address) *SignedAddress {
        return addr
 }
 
-// TransportPongMsg
+// TransportPongMsg is a reponse message for a PING request
 type TransportPongMsg struct {
        MsgSize     uint16         `order:"big"` // total size of message
        MsgType     uint16         `order:"big"` // TRANSPORT_PING (372)
@@ -193,8 +193,8 @@ func (m *TransportPongMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *TransportPongMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *TransportPongMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 // Sign the address block of a pong message.
@@ -238,7 +238,7 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
 // 4) address (address-length bytes)
 //----------------------------------------------------------------------
 
-// HelloAddress
+// HelloAddress represents a (generic) peer address with expiration date
 type HelloAddress struct {
        Transport string            // Name of transport
        AddrSize  uint16            `order:"big"` // Size of address entry
@@ -247,7 +247,7 @@ type HelloAddress struct {
 }
 
 // NewHelloAddress create a new HELLO address from the given address
-func NewAddress(a *util.Address) *HelloAddress {
+func NewHelloAddress(a *util.Address) *HelloAddress {
        addr := &HelloAddress{
                Transport: a.Transport,
                AddrSize:  uint16(len(a.Address)),
@@ -264,7 +264,7 @@ func (a *HelloAddress) String() string {
                util.AddressString(a.Transport, a.Address), a.ExpireOn)
 }
 
-// HelloMsg
+// HelloMsg is a message send by peers to announce their presence
 type HelloMsg struct {
        MsgSize    uint16          `order:"big"` // total size of message
        MsgType    uint16          `order:"big"` // HELLO (17)
@@ -300,15 +300,15 @@ func (m *HelloMsg) AddAddress(a *HelloAddress) {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *HelloMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *HelloMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_ACK
 //----------------------------------------------------------------------
 
-// SessionAckMsg
+// SessionAckMsg is a message to acknowlege a session request
 type SessionAckMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // TRANSPORT_SESSION_ACK (377)
@@ -328,15 +328,15 @@ func (m *SessionAckMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionAckMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionAckMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_SYN
 //----------------------------------------------------------------------
 
-// SessionSynMsg
+// SessionSynMsg is a synchronization request message for sessions
 type SessionSynMsg struct {
        MsgSize   uint16            `order:"big"` // total size of message
        MsgType   uint16            `order:"big"` // TRANSPORT_SESSION_SYN (375)
@@ -360,15 +360,15 @@ func (m *SessionSynMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionSynMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionSynMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_SYN_ACK
 //----------------------------------------------------------------------
 
-// SessionSynAckMsg
+// SessionSynAckMsg responds to a SYN request message
 type SessionSynAckMsg struct {
        MsgSize   uint16            `order:"big"` // total size of message
        MsgType   uint16            `order:"big"` // TRANSPORT_SESSION_SYN_ACK 
(376)
@@ -392,15 +392,15 @@ func (m *SessionSynAckMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionSynAckMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionSynAckMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_QUOTA
 //----------------------------------------------------------------------
 
-// SessionQuotaMsg
+// SessionQuotaMsg is a message to announce quotas for a session
 type SessionQuotaMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // TRANSPORT_SESSION_QUOTA (379)
@@ -424,15 +424,15 @@ func (m *SessionQuotaMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionQuotaMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionQuotaMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_KEEPALIVE
 //----------------------------------------------------------------------
 
-// SessionKeepAliveMsg
+// SessionKeepAliveMsg is a message send by peers to keep a session alive.
 type SessionKeepAliveMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // TRANSPORT_SESSION_KEEPALIVE (381)
@@ -455,15 +455,15 @@ func (m *SessionKeepAliveMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionKeepAliveMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionKeepAliveMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
 
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_KEEPALIVE_RESPONSE
 //----------------------------------------------------------------------
 
-// SessionKeepAliveRespMsg
+// SessionKeepAliveRespMsg is a response for a peer to a "keep-alive" request.
 type SessionKeepAliveRespMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
        MsgType uint16 `order:"big"` // TRANSPORT_SESSION_KEEPALIVE_RESPONSE 
(382)
@@ -486,6 +486,6 @@ func (m *SessionKeepAliveRespMsg) String() string {
 }
 
 // Header returns the message header in a separate instance.
-func (msg *SessionKeepAliveRespMsg) Header() *MessageHeader {
-       return &MessageHeader{msg.MsgSize, msg.MsgType}
+func (m *SessionKeepAliveRespMsg) Header() *Header {
+       return &Header{m.MsgSize, m.MsgType}
 }
diff --git a/src/gnunet/modules.go b/src/gnunet/modules.go
index 24dc92b..f4fdb1b 100644
--- a/src/gnunet/modules.go
+++ b/src/gnunet/modules.go
@@ -33,14 +33,23 @@ import (
        "gnunet/service/gns"
        "gnunet/service/namecache"
        "gnunet/service/revocation"
+       "net/rpc"
 )
 
-// List of all GNUnet service module instances
+// Instances holds a list of all GNUnet service modules
 type Instances struct {
-       GNS        *gns.GNSModule
+       GNS        *gns.Module
        Namecache  *namecache.NamecacheModule
-       DHT        *dht.DHTModule
-       Revocation *revocation.RevocationModule
+       DHT        *dht.Module
+       Revocation *revocation.Module
+}
+
+// Register modules for JSON-RPC
+func (inst Instances) Register() {
+       rpc.Register(inst.GNS)
+       rpc.Register(inst.Namecache)
+       rpc.Register(inst.DHT)
+       rpc.Register(inst.Revocation)
 }
 
 // Local reference to instance list
@@ -55,13 +64,13 @@ func init() {
        Modules.Namecache = new(namecache.NamecacheModule)
 
        // DHT (no calls to other modules)
-       Modules.DHT = new(dht.DHTModule)
+       Modules.DHT = new(dht.Module)
 
        // Revocation (no calls to other modules)
-       Modules.Revocation = revocation.NewRevocationModule()
+       Modules.Revocation = revocation.NewModule()
 
        // GNS (calls Namecache, DHT and Identity)
-       Modules.GNS = &gns.GNSModule{
+       Modules.GNS = &gns.Module{
                LookupLocal:      Modules.Namecache.Get,
                StoreLocal:       Modules.Namecache.Put,
                LookupRemote:     Modules.DHT.Get,
diff --git a/src/gnunet/rpc/server.go b/src/gnunet/rpc/server.go
new file mode 100644
index 0000000..760ecfc
--- /dev/null
+++ b/src/gnunet/rpc/server.go
@@ -0,0 +1,66 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 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 rpc
+
+import (
+       "context"
+       "gnunet/config"
+       "gnunet/service"
+       "net/http"
+       "time"
+
+       "github.com/bfix/gospel/logger"
+       "github.com/gorilla/mux"
+)
+
+// Router for JSON-RPC requests
+var Router = mux.NewRouter()
+var srv *http.Server
+
+// Start the JSON-RPC server. It can be terminated by context
+func Start(ctx context.Context) error {
+       // instantiate a server and run it
+       srv = &http.Server{
+               Handler:      Router,
+               Addr:         config.Cfg.RPC.Endpoint,
+               WriteTimeout: 15 * time.Second,
+               ReadTimeout:  15 * time.Second,
+       }
+       go func() {
+               // start listening
+               go func() {
+                       if err := srv.ListenAndServe(); err != nil {
+                               logger.Printf(logger.WARN, "[RPC] Server listen 
failed: %s", err.Error())
+                       }
+               }()
+               select {
+               case <-ctx.Done():
+                       if err := srv.Shutdown(context.Background()); err != 
nil {
+                               logger.Printf(logger.WARN, "[RPC] Server 
shutdownn failed: %s", err.Error())
+                       }
+               }
+       }()
+       return nil
+}
+
+// Register a JSON-RPC path in a service-specific processor
+func Register(m service.Module) {
+       path, hdlr := m.RPC()
+       Router.HandleFunc(path, hdlr)
+}
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index b153bab..a196c09 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -59,9 +59,9 @@ func (c *Client) Close() error {
        return c.ch.Close()
 }
 
-// ServiceRequestResponse is a helper method for a one request - one response
+// RequestResponse is a helper method for a one request - one response
 // secenarios of client/serice interactions.
-func ServiceRequestResponse(
+func RequestResponse(
        ctx *SessionContext,
        caller string,
        callee string,
diff --git a/src/gnunet/service/context.go b/src/gnunet/service/context.go
index 120d460..4ae786d 100644
--- a/src/gnunet/service/context.go
+++ b/src/gnunet/service/context.go
@@ -30,7 +30,7 @@ import (
 // by a service; the session is handled by the 'ServeClient' method of a
 // service implementation.
 type SessionContext struct {
-       Id       int                   // session identifier
+       ID       int                   // session identifier
        wg       *sync.WaitGroup       // wait group for the session
        sig      *concurrent.Signaller // signaller for the session
        pending  int                   // number of pending go-routines
@@ -41,7 +41,7 @@ type SessionContext struct {
 // NewSessionContext instantiates a new session context.
 func NewSessionContext() *SessionContext {
        return &SessionContext{
-               Id:       util.NextID(),
+               ID:       util.NextID(),
                wg:       new(sync.WaitGroup),
                sig:      concurrent.NewSignaller(),
                pending:  0,
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 61c2540..1588a06 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -32,14 +32,16 @@ import (
 // Put and get blocks into/from a DHT.
 //----------------------------------------------------------------------
 
-// DHT handles the permanent storage of blocks under the query key.
-type DHTModule struct {
+// Module handles the permanent storage of blocks under the query key.
+type Module struct {
 }
 
-func (nc *DHTModule) Get(ctx *service.SessionContext, query *gns.Query) 
(*message.GNSBlock, error) {
+// Get a GNS block from the DHT
+func (nc *Module) Get(ctx *service.SessionContext, query *gns.Query) 
(*message.Block, error) {
        return nil, nil
 }
 
-func (nc *DHTModule) Put(ctx *service.SessionContext, block *message.GNSBlock) 
error {
+// Put a GNS block into the DHT
+func (nc *Module) Put(ctx *service.SessionContext, block *message.Block) error 
{
        return nil
 }
diff --git a/src/gnunet/service/gns/block_handler.go 
b/src/gnunet/service/gns/block_handler.go
index 3b3438d..b05c59e 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -22,32 +22,33 @@ import (
        "encoding/hex"
        "fmt"
 
+       "gnunet/crypto"
        "gnunet/enums"
        "gnunet/message"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/logger"
 )
 
 // HdlrInst is the type for functions that instanciate custom block handlers.
-type HdlrInst func(*message.GNSResourceRecord, []string) (BlockHandler, error)
+type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error)
 
 // Error codes
 var (
-       ErrInvalidRecordType = fmt.Errorf("Invalid resource record type")
-       ErrInvalidRecordBody = fmt.Errorf("Invalid resource record body")
-       ErrInvalidPKEY       = fmt.Errorf("Invalid PKEY resource record")
-       ErrInvalidCNAME      = fmt.Errorf("Invalid CNAME resource record")
-       ErrInvalidVPN        = fmt.Errorf("Invalid VPN resource record")
-       ErrInvalidRecordMix  = fmt.Errorf("Invalid mix of RR types in block")
-       ErrBlockHandler      = fmt.Errorf("Internal block handler failure")
+       ErrInvalidRecordType = fmt.Errorf("invalid resource record type")
+       ErrInvalidRecordBody = fmt.Errorf("invalid resource record body")
+       ErrInvalidZoneKey    = fmt.Errorf("invalid zone key resource record")
+       ErrInvalidCNAME      = fmt.Errorf("invalid CNAME resource record")
+       ErrInvalidVPN        = fmt.Errorf("invalid VPN resource record")
+       ErrInvalidRecordMix  = fmt.Errorf("invalid mix of RR types in block")
+       ErrBlockHandler      = fmt.Errorf("internal block handler failure")
 )
 
 // Mapping of RR types to BlockHandler instanciation functions
 var (
        customHandler = map[int]HdlrInst{
-               enums.GNS_TYPE_PKEY:      NewPkeyHandler,
+               enums.GNS_TYPE_PKEY:      NewZoneHandler,
+               enums.GNS_TYPE_EDKEY:     NewZoneHandler,
                enums.GNS_TYPE_GNS2DNS:   NewGns2DnsHandler,
                enums.GNS_TYPE_BOX:       NewBoxHandler,
                enums.GNS_TYPE_LEHO:      NewLehoHandler,
@@ -69,7 +70,7 @@ type BlockHandler interface {
        // processing. The handler can inspect the remaining labels in a path
        // if required. The method returns an error if a record is not accepted
        // by the block handler (RR not of required type).
-       AddRecord(rr *message.GNSResourceRecord, labels []string) error
+       AddRecord(rr *message.ResourceRecord, labels []string) error
 
        // Coexist checks if a custom block handler can co-exist with other
        // resource records in the same block. 'cm' maps the resource type
@@ -79,7 +80,7 @@ type BlockHandler interface {
 
        // Records returns a list of RR of the given types associated with
        // the custom handler
-       Records(kind RRTypeList) *message.GNSRecordSet
+       Records(kind RRTypeList) *message.RecordSet
 
        // Name returns the human-readable name of the handler
        Name() string
@@ -107,7 +108,7 @@ type BlockHandlerList struct {
 
 // NewBlockHandlerList instantiates an a list of active block handlers
 // for a given set of records (GNS block).
-func NewBlockHandlerList(records []*message.GNSResourceRecord, labels 
[]string) (*BlockHandlerList, []*message.GNSResourceRecord, error) {
+func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) 
(*BlockHandlerList, []*message.ResourceRecord, error) {
        // initialize block handler list
        hl := &BlockHandlerList{
                list:   make(map[int]BlockHandler),
@@ -115,7 +116,7 @@ func NewBlockHandlerList(records 
[]*message.GNSResourceRecord, labels []string)
        }
 
        // first pass: build list of shadow records in this block
-       shadows := make([]*message.GNSResourceRecord, 0)
+       shadows := make([]*message.ResourceRecord, 0)
        for _, rec := range records {
                // filter out shadow records...
                if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 {
@@ -124,7 +125,7 @@ func NewBlockHandlerList(records 
[]*message.GNSResourceRecord, labels []string)
        }
        // second pass: normalize block by filtering out expired records (and
        // replacing them with shadow records if available
-       active := make([]*message.GNSResourceRecord, 0)
+       active := make([]*message.ResourceRecord, 0)
        for _, rec := range records {
                // don't process shadow records again
                if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 {
@@ -190,41 +191,48 @@ func NewBlockHandlerList(records 
[]*message.GNSResourceRecord, labels []string)
        return hl, active, nil
 }
 
-// GetHandler returns a BlockHandler for the given key. If no block handler 
exists
-// under the given name, a new one is created and stored in the list. The type 
of
-// the new block handler is derived from the key value.
-func (hl *BlockHandlerList) GetHandler(t int) BlockHandler {
-       // return handler for given key if it exists
-       if hdlr, ok := hl.list[t]; ok {
-               return hdlr
+// GetHandler returns a BlockHandler for the given GNS block type.
+// If more than one type is given, the first matching hanlder is
+// returned.
+func (hl *BlockHandlerList) GetHandler(types ...int) BlockHandler {
+       for _, t := range types {
+               // return handler for given type if it exists
+               if hdlr, ok := hl.list[t]; ok {
+                       return hdlr
+               }
        }
        return nil
 }
 
 // FinalizeRecord post-processes records
-func (hl *BlockHandlerList) FinalizeRecord(rec *message.GNSResourceRecord) 
*message.GNSResourceRecord {
+func (hl *BlockHandlerList) FinalizeRecord(rec *message.ResourceRecord) 
*message.ResourceRecord {
        // no implementation yet
        return rec
 }
 
 //----------------------------------------------------------------------
-// PKEY handler: Only one PKEY as sole record in a block
+// Zone key handler: Only one zone key as sole record in a block
 //----------------------------------------------------------------------
 
-// PkeyHandler implementing the BlockHandler interface
-type PkeyHandler struct {
-       pkey *ed25519.PublicKey         // Zone key
-       rec  *message.GNSResourceRecord // associated recource record
+// ZoneKeyHandler implementing the BlockHandler interface
+type ZoneKeyHandler struct {
+       ztype uint32                  // zone type
+       zkey  *crypto.ZoneKey         // Zone key
+       rec   *message.ResourceRecord // associated recource record
 }
 
-// NewPkeyHandler returns a new BlockHandler instance
-func NewPkeyHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
-       if int(rec.Type) != enums.GNS_TYPE_PKEY {
+// NewZoneHandler returns a new BlockHandler instance
+func NewZoneHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
+       // check if we have an implementation for the zone type
+       if crypto.GetImplementation(rec.Type) == nil {
                return nil, ErrInvalidRecordType
        }
-       h := &PkeyHandler{
-               pkey: nil,
+       // assemble handler
+       h := &ZoneKeyHandler{
+               ztype: rec.Type,
+               zkey:  nil,
        }
+       // add the record to the handler
        if err := h.AddRecord(rec, labels); err != nil {
                return nil, err
        }
@@ -232,34 +240,34 @@ func NewPkeyHandler(rec *message.GNSResourceRecord, 
labels []string) (BlockHandl
 }
 
 // AddRecord inserts a PKEY record into the handler.
-func (h *PkeyHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
-       if int(rec.Type) != enums.GNS_TYPE_PKEY {
+func (h *ZoneKeyHandler) AddRecord(rec *message.ResourceRecord, labels 
[]string) (err error) {
+       // check record type
+       if rec.Type != h.ztype {
                return ErrInvalidRecordType
        }
-       // check for sole PKEY record in block
-       if h.pkey != nil {
-               return ErrInvalidPKEY
+       // check for sole zone key record in block
+       if h.zkey != nil {
+               return ErrInvalidZoneKey
        }
-       // check for sane key data
-       if len(rec.Data) != 32 {
-               return ErrInvalidPKEY
+       // set zone key
+       h.zkey, err = crypto.NewZoneKey(rec.Data)
+       if err != nil {
+               return
        }
-       // set a PKEY handler
-       h.pkey = ed25519.NewPublicKeyFromBytes(rec.Data)
        h.rec = rec
-       return nil
+       return
 }
 
 // Coexist return a flag indicating how a resource record of a given type
 // is to be treated (see BlockHandler interface)
-func (h *PkeyHandler) Coexist(cm util.CounterMap) bool {
+func (h *ZoneKeyHandler) Coexist(cm util.CounterMap) bool {
        // only one type (GNS_TYPE_PKEY) is present
        return len(cm) == 1 && cm.Num(enums.GNS_TYPE_PKEY) == 1
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *PkeyHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *ZoneKeyHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        if kind.HasType(enums.GNS_TYPE_PKEY) {
                rs.AddRecord(h.rec)
        }
@@ -267,7 +275,7 @@ func (h *PkeyHandler) Records(kind RRTypeList) 
*message.GNSRecordSet {
 }
 
 // Name returns the human-readable name of the handler.
-func (h *PkeyHandler) Name() string {
+func (h *ZoneKeyHandler) Name() string {
        return "PKEY_Handler"
 }
 
@@ -277,20 +285,20 @@ func (h *PkeyHandler) Name() string {
 
 // Gns2DnsHandler implementing the BlockHandler interface
 type Gns2DnsHandler struct {
-       Query   string                       // DNS query name
-       Servers []string                     // DNS servers to ask
-       recs    []*message.GNSResourceRecord // list of rersource records
+       Query   string                    // DNS query name
+       Servers []string                  // DNS servers to ask
+       recs    []*message.ResourceRecord // list of rersource records
 }
 
 // NewGns2DnsHandler returns a new BlockHandler instance
-func NewGns2DnsHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
+func NewGns2DnsHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
        if int(rec.Type) != enums.GNS_TYPE_GNS2DNS {
                return nil, ErrInvalidRecordType
        }
        h := &Gns2DnsHandler{
                Query:   "",
                Servers: make([]string, 0),
-               recs:    make([]*message.GNSResourceRecord, 0),
+               recs:    make([]*message.ResourceRecord, 0),
        }
        if err := h.AddRecord(rec, labels); err != nil {
                return nil, err
@@ -299,7 +307,7 @@ func NewGns2DnsHandler(rec *message.GNSResourceRecord, 
labels []string) (BlockHa
 }
 
 // AddRecord inserts a GNS2DNS record into the handler.
-func (h *Gns2DnsHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
+func (h *Gns2DnsHandler) AddRecord(rec *message.ResourceRecord, labels 
[]string) error {
        if int(rec.Type) != enums.GNS_TYPE_GNS2DNS {
                return ErrInvalidRecordType
        }
@@ -333,8 +341,8 @@ func (h *Gns2DnsHandler) Coexist(cm util.CounterMap) bool {
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *Gns2DnsHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *Gns2DnsHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        if kind.HasType(enums.GNS_TYPE_GNS2DNS) {
                for _, rec := range h.recs {
                        rs.AddRecord(rec)
@@ -358,7 +366,7 @@ type BoxHandler struct {
 }
 
 // NewBoxHandler returns a new BlockHandler instance
-func NewBoxHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
+func NewBoxHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
        if int(rec.Type) != enums.GNS_TYPE_BOX {
                return nil, ErrInvalidRecordType
        }
@@ -372,7 +380,7 @@ func NewBoxHandler(rec *message.GNSResourceRecord, labels 
[]string) (BlockHandle
 }
 
 // AddRecord inserts a BOX record into the handler.
-func (h *BoxHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
+func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string) 
error {
        if int(rec.Type) != enums.GNS_TYPE_BOX {
                return ErrInvalidRecordType
        }
@@ -403,12 +411,12 @@ func (h *BoxHandler) Coexist(cm util.CounterMap) bool {
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *BoxHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *BoxHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        for _, box := range h.boxes {
                if kind.HasType(int(box.Type)) {
                        // valid box found: assemble new resource record.
-                       rr := new(message.GNSResourceRecord)
+                       rr := new(message.ResourceRecord)
                        rr.Expires = box.rec.Expires
                        rr.Flags = box.rec.Flags
                        rr.Type = box.Type
@@ -432,11 +440,11 @@ func (h *BoxHandler) Name() string {
 // LehoHandler implementing the BlockHandler interface
 type LehoHandler struct {
        name string
-       rec  *message.GNSResourceRecord
+       rec  *message.ResourceRecord
 }
 
 // NewLehoHandler returns a new BlockHandler instance
-func NewLehoHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
+func NewLehoHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
        if int(rec.Type) != enums.GNS_TYPE_LEHO {
                return nil, ErrInvalidRecordType
        }
@@ -450,7 +458,7 @@ func NewLehoHandler(rec *message.GNSResourceRecord, labels 
[]string) (BlockHandl
 }
 
 // AddRecord inserts a LEHO record into the handler.
-func (h *LehoHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
+func (h *LehoHandler) AddRecord(rec *message.ResourceRecord, labels []string) 
error {
        if int(rec.Type) != enums.GNS_TYPE_LEHO {
                return ErrInvalidRecordType
        }
@@ -467,8 +475,8 @@ func (h *LehoHandler) Coexist(cm util.CounterMap) bool {
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *LehoHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *LehoHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        if kind.HasType(enums.GNS_TYPE_LEHO) {
                rs.AddRecord(h.rec)
        }
@@ -487,11 +495,11 @@ func (h *LehoHandler) Name() string {
 // CnameHandler implementing the BlockHandler interface
 type CnameHandler struct {
        name string
-       rec  *message.GNSResourceRecord
+       rec  *message.ResourceRecord
 }
 
 // NewCnameHandler returns a new BlockHandler instance
-func NewCnameHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
+func NewCnameHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
        if int(rec.Type) != enums.GNS_TYPE_DNS_CNAME {
                return nil, ErrInvalidRecordType
        }
@@ -505,7 +513,7 @@ func NewCnameHandler(rec *message.GNSResourceRecord, labels 
[]string) (BlockHand
 }
 
 // AddRecord inserts a CNAME record into the handler.
-func (h *CnameHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
+func (h *CnameHandler) AddRecord(rec *message.ResourceRecord, labels []string) 
error {
        if int(rec.Type) != enums.GNS_TYPE_DNS_CNAME {
                return ErrInvalidRecordType
        }
@@ -525,8 +533,8 @@ func (h *CnameHandler) Coexist(cm util.CounterMap) bool {
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *CnameHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *CnameHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        if kind.HasType(enums.GNS_TYPE_DNS_CNAME) {
                rs.AddRecord(h.rec)
        }
@@ -544,11 +552,11 @@ func (h *CnameHandler) Name() string {
 
 // VpnHandler implementing the BlockHandler interface
 type VpnHandler struct {
-       rec *message.GNSResourceRecord
+       rec *message.ResourceRecord
 }
 
 // NewVpnHandler returns a new BlockHandler instance
-func NewVpnHandler(rec *message.GNSResourceRecord, labels []string) 
(BlockHandler, error) {
+func NewVpnHandler(rec *message.ResourceRecord, labels []string) 
(BlockHandler, error) {
        if int(rec.Type) != enums.GNS_TYPE_VPN {
                return nil, ErrInvalidRecordType
        }
@@ -560,7 +568,7 @@ func NewVpnHandler(rec *message.GNSResourceRecord, labels 
[]string) (BlockHandle
 }
 
 // AddRecord inserts a VPN record into the handler.
-func (h *VpnHandler) AddRecord(rec *message.GNSResourceRecord, labels 
[]string) error {
+func (h *VpnHandler) AddRecord(rec *message.ResourceRecord, labels []string) 
error {
        if int(rec.Type) != enums.GNS_TYPE_VPN {
                return ErrInvalidRecordType
        }
@@ -579,8 +587,8 @@ func (h *VpnHandler) Coexist(cm util.CounterMap) bool {
 }
 
 // Records returns a list of RR of the given type associated with this handler
-func (h *VpnHandler) Records(kind RRTypeList) *message.GNSRecordSet {
-       rs := message.NewGNSRecordSet()
+func (h *VpnHandler) Records(kind RRTypeList) *message.RecordSet {
+       rs := message.NewRecordSet()
        if kind.HasType(enums.GNS_TYPE_VPN) {
                rs.AddRecord(h.rec)
        }
diff --git a/src/gnunet/service/gns/box.go b/src/gnunet/service/gns/box.go
index 306ca68..1afd843 100644
--- a/src/gnunet/service/gns/box.go
+++ b/src/gnunet/service/gns/box.go
@@ -37,12 +37,12 @@ type Box struct {
        RR    []byte `size:"*"`    // embedded RR
 
        // transient attributes (not serialized)
-       key string                     // map key for box instance
-       rec *message.GNSResourceRecord // originating RR
+       key string                  // map key for box instance
+       rec *message.ResourceRecord // originating RR
 }
 
 // NewBox creates a new box instance from a BOX resource record.
-func NewBox(rec *message.GNSResourceRecord) *Box {
+func NewBox(rec *message.ResourceRecord) *Box {
        b := new(Box)
        if err := data.Unmarshal(b, rec.Data); err != nil {
                logger.Printf(logger.ERROR, "[gns] Can't unmarshal BOX")
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 7a9a30c..3bcc35e 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -24,21 +24,21 @@ import (
        "strings"
        "time"
 
+       "gnunet/crypto"
        "gnunet/enums"
        "gnunet/message"
        "gnunet/service"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/logger"
        "github.com/miekg/dns"
 )
 
 // Error codes
 var (
-       ErrDNSTimedOut  = fmt.Errorf("DNS query timed out")
-       ErrNoDNSQueries = fmt.Errorf("No valid DNS queries")
-       ErrNoDNSResults = fmt.Errorf("No valid DNS results")
+       ErrDNSTimedOut  = fmt.Errorf("query timed out (DNS)")
+       ErrNoDNSQueries = fmt.Errorf("no valid DNS queries")
+       ErrNoDNSResults = fmt.Errorf("no valid DNS results")
 )
 
 //----------------------------------------------------------------------
@@ -48,7 +48,7 @@ var (
 // RRTypeList is a list of integers representing RR types.
 type RRTypeList []int
 
-// Initialize a new type list with given type values
+// NewRRTypeList initializes a new type list with given type values
 func NewRRTypeList(args ...int) (res RRTypeList) {
        for _, val := range args {
                // if GNS_TYPE_ANY is encountered, it becomes the sole type
@@ -92,7 +92,7 @@ func (tl RRTypeList) HasType(t int) bool {
 // Helper functions
 //----------------------------------------------------------------------
 
-// Convert DNS name from its binary representation [RFC1034]:
+// DNSNameFromBytes converts DNS name from its binary representation [RFC1034]:
 // A string is a sequence of a (len,chars...) tupels terminated by a (len=0,).
 // The name parts are concatenated with "." as separator.
 // The parsing starts at offset in the byte array; the function returns the
@@ -115,7 +115,8 @@ func DNSNameFromBytes(b []byte, offset int) (int, string) {
        return pos + 1, str
 }
 
-func QueryDNS(id int, name string, server net.IP, kind RRTypeList) 
*message.GNSRecordSet {
+// QueryDNS queries the specified DNS server for a given name and expected 
result types.
+func QueryDNS(id int, name string, server net.IP, kind RRTypeList) 
*message.RecordSet {
        // get default nameserver if not defined.
        if server == nil {
                server = net.IPv4(8, 8, 8, 8)
@@ -134,9 +135,9 @@ func QueryDNS(id int, name string, server net.IP, kind 
RRTypeList) *message.GNSR
                Question: make([]dns.Question, 1),
        }
        m.Question[0] = dns.Question{
-               dns.Fqdn(name),
-               dns.TypeANY,
-               dns.ClassINET,
+               Name:   dns.Fqdn(name),
+               Qtype:  dns.TypeANY,
+               Qclass: dns.ClassINET,
        }
 
        // perform query in retry-loop
@@ -160,7 +161,7 @@ func QueryDNS(id int, name string, server net.IP, kind 
RRTypeList) *message.GNSR
                        logger.Printf(logger.ERROR, "[dns][%d] No results\n", 
id)
                        return nil
                }
-               set := message.NewGNSRecordSet()
+               set := message.NewRecordSet()
                for _, record := range in.Answer {
                        // check if answer record is of requested type
                        if kind.HasType(int(record.Header().Rrtype)) {
@@ -173,7 +174,7 @@ func QueryDNS(id int, name string, server net.IP, kind 
RRTypeList) *message.GNSR
                                }
 
                                // create a new GNS resource record
-                               rr := new(message.GNSResourceRecord)
+                               rr := new(message.ResourceRecord)
                                expires := 
time.Now().Add(time.Duration(record.Header().Ttl) * time.Second)
                                rr.Expires = util.NewAbsoluteTime(expires)
                                rr.Flags = 0
@@ -203,17 +204,17 @@ func QueryDNS(id int, name string, server net.IP, kind 
RRTypeList) *message.GNSR
 // 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 *GNSModule) ResolveDNS(
+func (gns *Module) ResolveDNS(
        ctx *service.SessionContext,
        name string,
        servers []string,
        kind RRTypeList,
-       pkey *ed25519.PublicKey,
-       depth int) (set *message.GNSRecordSet, err error) {
+       zkey *crypto.ZoneKey,
+       depth int) (set *message.RecordSet, err error) {
 
        // start DNS queries concurrently
        logger.Printf(logger.DBG, "[dns] Resolution of '%s' starting...\n", 
name)
-       res := make(chan *message.GNSRecordSet)
+       res := make(chan *message.RecordSet)
        running := 0
        for _, srv := range servers {
                // check if srv is an IPv4/IPv6 address
@@ -222,7 +223,7 @@ func (gns *GNSModule) 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, pkey, 
query, depth+1); err != nil {
+                       if set, err = gns.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
                        }
@@ -255,7 +256,7 @@ func (gns *GNSModule) ResolveDNS(
                return nil, ErrNoDNSQueries
        }
        // wait for query results
-       timeout := time.Tick(10 * time.Second)
+       timeout := time.NewTicker(10 * time.Second)
        for {
                select {
                case set = <-res:
@@ -271,7 +272,7 @@ func (gns *GNSModule) ResolveDNS(
                                return nil, ErrNoDNSResults
                        }
 
-               case <-timeout:
+               case <-timeout.C:
                        // no results
                        logger.Println(logger.WARN, "[dns] Queries timed out.")
                        return nil, ErrNoDNSResults
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index 5e787d5..5ff81f2 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -20,6 +20,7 @@ package gns
 
 import (
        "fmt"
+       "net/http"
        "strings"
 
        "gnunet/config"
@@ -30,7 +31,6 @@ import (
        "gnunet/service/revocation"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/logger"
 )
 
@@ -40,8 +40,8 @@ import (
 
 // Error codes
 var (
-       ErrUnknownTLD           = fmt.Errorf("Unknown TLD in name")
-       ErrGNSRecursionExceeded = fmt.Errorf("GNS recursion depth exceeded")
+       ErrUnknownTLD           = fmt.Errorf("unknown TLD in name")
+       ErrGNSRecursionExceeded = fmt.Errorf("recursion depth exceeded")
 )
 
 //----------------------------------------------------------------------
@@ -51,20 +51,21 @@ var (
 // Query specifies the context for a basic GNS name lookup of an (atomic)
 // label in a given zone identified by its public key.
 type Query struct {
-       Zone    *ed25519.PublicKey // Public zone key
-       Label   string             // Atomic label
-       Derived *ed25519.PublicKey // Derived key from (pkey,label)
-       Key     *crypto.HashCode   // Key for repository queries (local/remote)
+       Zone    *crypto.ZoneKey  // Public zone key
+       Label   string           // Atomic label
+       Derived *crypto.ZoneKey  // Derived key from (pkey,label)
+       Key     *crypto.HashCode // Key for repository queries (local/remote)
 }
 
 // NewQuery assembles a new Query object for the given zone and label.
-func NewQuery(pkey *ed25519.PublicKey, label string) *Query {
+func NewQuery(zkey *crypto.ZoneKey, label string) *Query {
        // derive a public key from (pkey,label) and set the repository
        // key as the SHA512 hash of the binary key representation.
-       pd := crypto.DerivePublicKey(pkey, label, "gns")
+       // (key blinding)
+       pd, _ := zkey.Derive(label, "gns")
        key := crypto.Hash(pd.Bytes())
        return &Query{
-               Zone:    pkey,
+               Zone:    zkey,
                Label:   label,
                Derived: pd,
                Key:     key,
@@ -109,25 +110,32 @@ func NewQuery(pkey *ed25519.PublicKey, label string) 
*Query {
 //      resource record types).
 //----------------------------------------------------------------------
 
-// GNSModule handles the resolution of GNS names to RRs bundled in a block.
-type GNSModule struct {
+// Module handles the resolution of GNS names to RRs bundled in a block.
+type Module struct {
        // Use function references for calls to methods in other modules:
-       LookupLocal      func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
-       StoreLocal       func(ctx *service.SessionContext, block 
*message.GNSBlock) error
-       LookupRemote     func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
-       RevocationQuery  func(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error)
+       LookupLocal      func(ctx *service.SessionContext, query *Query) 
(*message.Block, error)
+       StoreLocal       func(ctx *service.SessionContext, block 
*message.Block) error
+       LookupRemote     func(ctx *service.SessionContext, query *Query) 
(*message.Block, error)
+       RevocationQuery  func(ctx *service.SessionContext, zkey 
*crypto.ZoneKey) (valid bool, err error)
        RevocationRevoke func(ctx *service.SessionContext, rd 
*revocation.RevData) (success bool, err error)
 }
 
+// RPC returns the route and handler function for a JSON-RPC request
+func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
+       return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
+               wrt.Write([]byte(`{"msg": "This is GNS" }`))
+       }
+}
+
 // Resolve a GNS name with multiple labels. If pkey is not nil, the name
 // is interpreted as "relative to current zone".
-func (gns *GNSModule) Resolve(
+func (m *Module) Resolve(
        ctx *service.SessionContext,
        path string,
-       pkey *ed25519.PublicKey,
+       zkey *crypto.ZoneKey,
        kind RRTypeList,
        mode int,
-       depth int) (set *message.GNSRecordSet, err error) {
+       depth int) (set *message.RecordSet, err error) {
 
        // check for recursion depth
        if depth > config.Cfg.GNS.MaxDepth {
@@ -138,60 +146,62 @@ func (gns *GNSModule) Resolve(
        logger.Printf(logger.DBG, "[gns] Resolver called for %v\n", names)
 
        // check for relative path
-       if pkey != nil {
+       if zkey != nil {
                //resolve relative path
-               return gns.ResolveRelative(ctx, names, pkey, kind, mode, depth)
+               return m.ResolveRelative(ctx, names, zkey, kind, mode, depth)
        }
        // resolve absolute path
-       return gns.ResolveAbsolute(ctx, names, kind, mode, depth)
+       return m.ResolveAbsolute(ctx, names, kind, mode, depth)
 }
 
-// Resolve a fully qualified GNS absolute name (with multiple labels).
-func (gns *GNSModule) ResolveAbsolute(
+// ResolveAbsolute resolves a fully qualified GNS absolute name
+// (with multiple labels).
+func (m *Module) ResolveAbsolute(
        ctx *service.SessionContext,
        labels []string,
        kind RRTypeList,
        mode int,
-       depth int) (set *message.GNSRecordSet, err error) {
+       depth int) (set *message.RecordSet, err error) {
 
        // get the zone key for the TLD
-       pkey := gns.GetZoneKey(labels[0])
-       if pkey == nil {
+       zkey := m.GetZoneKey(labels[0])
+       if zkey == nil {
                // we can't resolve this TLD
                err = ErrUnknownTLD
                return
        }
        // check if zone key has been revoked
        var valid bool
-       set = message.NewGNSRecordSet()
-       if valid, err = gns.RevocationQuery(ctx, pkey); err != nil || !valid {
+       set = message.NewRecordSet()
+       if valid, err = m.RevocationQuery(ctx, zkey); err != nil || !valid {
                return
        }
        // continue with resolution relative to a zone.
-       return gns.ResolveRelative(ctx, labels[1:], pkey, kind, mode, depth)
+       return m.ResolveRelative(ctx, labels[1:], zkey, kind, mode, depth)
 }
 
-// Resolve relative path (to a given zone) recursively by processing simple
-// (PKEY,Label) lookups in sequence and handle intermediate GNS record types
-func (gns *GNSModule) ResolveRelative(
+// ResolveRelative resolves a relative path (to a given zone) recursively by
+// processing simple (PKEY,Label) lookups in sequence and handle intermediate
+// GNS record types
+func (m *Module) ResolveRelative(
        ctx *service.SessionContext,
        labels []string,
-       pkey *ed25519.PublicKey,
+       zkey *crypto.ZoneKey,
        kind RRTypeList,
        mode int,
-       depth int) (set *message.GNSRecordSet, err error) {
+       depth int) (set *message.RecordSet, err error) {
 
        // Process all names in sequence
        var (
-               records []*message.GNSResourceRecord // final resource records 
from resolution
-               hdlrs   *BlockHandlerList            // list of block handlers 
in final step
+               records []*message.ResourceRecord // final resource records 
from resolution
+               hdlrs   *BlockHandlerList         // list of block handlers in 
final step
        )
        for ; len(labels) > 0; labels = labels[1:] {
-               logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in 
'%s'\n", labels[0], util.EncodeBinaryToString(pkey.Bytes()))
+               logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in 
'%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes()))
 
                // resolve next level
-               var block *message.GNSBlock
-               if block, err = gns.Lookup(ctx, pkey, labels[0], mode); err != 
nil {
+               var block *message.Block
+               if block, err = m.Lookup(ctx, zkey, labels[0], mode); err != 
nil {
                        // failed to resolve name
                        return
                }
@@ -200,7 +210,7 @@ func (gns *GNSModule) ResolveRelative(
                        // if we have no results at this point, return NXDOMAIN
                        if block == nil {
                                // return record set with no entries as signal 
for NXDOMAIN
-                               set = message.NewGNSRecordSet()
+                               set = message.NewRecordSet()
                                return
                        }
                        mode = enums.GNS_LO_DEFAULT
@@ -228,20 +238,22 @@ func (gns *GNSModule) ResolveRelative(
                // handle special block cases in priority order:
                //--------------------------------------------------------------
 
-               if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_PKEY); hdlr != nil {
-                       // (1) PKEY record:
-                       inst := hdlr.(*PkeyHandler)
+               if hdlr := hdlrs.GetHandler(
+                       enums.GNS_TYPE_PKEY,
+                       enums.GNS_TYPE_EDKEY,
+               ); hdlr != nil {
+                       // (1) zone key record:
+                       inst := hdlr.(*ZoneKeyHandler)
                        // if labels are pending, set new zone and continue 
resolution;
-                       // otherwise resolve "@" label for the zone if no PKEY 
record
+                       // otherwise resolve "@" label for the zone if no zone 
key record
                        // was requested.
-                       pkey = inst.pkey
                        if len(labels) == 1 && 
!kind.HasType(enums.GNS_TYPE_PKEY) {
                                labels = append(labels, "@")
                        }
                        // check if zone key has been revoked
-                       if valid, err := gns.RevocationQuery(ctx, pkey); err != 
nil || !valid {
+                       if valid, err := m.RevocationQuery(ctx, inst.zkey); err 
!= nil || !valid {
                                // revoked key -> no results!
-                               records = make([]*message.GNSResourceRecord, 0)
+                               records = make([]*message.ResourceRecord, 0)
                                break
                        }
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); 
hdlr != nil {
@@ -260,7 +272,7 @@ func (gns *GNSModule) ResolveRelative(
                                lbls += "."
                        }
                        fqdn := lbls + inst.Query
-                       if set, err = gns.ResolveDNS(ctx, fqdn, inst.Servers, 
kind, pkey, depth); err != nil {
+                       if set, err = m.ResolveDNS(ctx, fqdn, inst.Servers, 
kind, zkey, depth); err != nil {
                                logger.Println(logger.ERROR, "[gns] GNS2DNS 
resolution failed.")
                                return
                        }
@@ -275,7 +287,7 @@ func (gns *GNSModule) ResolveRelative(
                                                expires = rec.Expires
                                        }
                                }
-                               set.Records = append(set.Records, 
gns.newLEHORecord(inst.Query, expires))
+                               set.Records = append(set.Records, 
m.newLEHORecord(inst.Query, expires))
                        }
                        // we are done with resolution; pass on records to 
caller
                        records = set.Records
@@ -283,9 +295,9 @@ func (gns *GNSModule) ResolveRelative(
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_BOX); hdlr != 
nil {
                        // (3) BOX records:
                        inst := hdlr.(*BoxHandler)
-                       new_records := inst.Records(kind).Records
-                       if len(new_records) > 0 {
-                               records = new_records
+                       newRecords := inst.Records(kind).Records
+                       if len(newRecords) > 0 {
+                               records = newRecords
                                break
                        }
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_DNS_CNAME); 
hdlr != nil {
@@ -298,7 +310,7 @@ func (gns *GNSModule) ResolveRelative(
                                break
                        }
                        logger.Println(logger.DBG, "[gns] CNAME resolution 
required.")
-                       if set, err = gns.ResolveUnknown(ctx, inst.name, 
labels, pkey, kind, depth+1); err != nil {
+                       if set, err = m.ResolveUnknown(ctx, inst.name, labels, 
zkey, kind, depth+1); err != nil {
                                logger.Println(logger.ERROR, "[gns] CNAME 
resolution failed.")
                                return
                        }
@@ -309,7 +321,7 @@ func (gns *GNSModule) ResolveRelative(
        }
        // Assemble resulting resource record set by filtering for requested 
types.
        // Records might get transformed by active block handlers.
-       set = message.NewGNSRecordSet()
+       set = message.NewRecordSet()
        for _, rec := range records {
                // is this the record type we are looking for?
                if kind.HasType(int(rec.Type)) {
@@ -348,13 +360,13 @@ func (gns *GNSModule) ResolveRelative(
 // relative to the zone PKEY. If the name is an absolute GNS name (ending in
 // a PKEY TLD), it is also resolved with GNS. All other names are resolved
 // via DNS queries.
-func (gns *GNSModule) ResolveUnknown(
+func (m *Module) ResolveUnknown(
        ctx *service.SessionContext,
        name string,
        labels []string,
-       pkey *ed25519.PublicKey,
+       zkey *crypto.ZoneKey,
        kind RRTypeList,
-       depth int) (set *message.GNSRecordSet, err error) {
+       depth int) (set *message.RecordSet, err error) {
 
        // relative GNS-based server name?
        if strings.HasSuffix(name, ".+") {
@@ -363,14 +375,14 @@ func (gns *GNSModule) ResolveUnknown(
                for _, label := range util.ReverseStringList(labels) {
                        name += "." + label
                }
-               if set, err = gns.Resolve(ctx, name, pkey, kind, 
enums.GNS_LO_DEFAULT, depth+1); err != nil {
+               if set, err = m.Resolve(ctx, name, zkey, kind, 
enums.GNS_LO_DEFAULT, depth+1); err != nil {
                        return
                }
        } else {
                // check for absolute GNS name (with PKEY as TLD)
-               if zk := gns.GetZoneKey(name); zk != nil {
+               if zk := m.GetZoneKey(name); zk != nil {
                        // resolve absolute GNS name (name ends in a PKEY)
-                       if set, err = gns.Resolve(ctx, 
util.StripPathRight(name), zk, kind, enums.GNS_LO_DEFAULT, depth+1); err != nil 
{
+                       if set, err = m.Resolve(ctx, util.StripPathRight(name), 
zk, kind, enums.GNS_LO_DEFAULT, depth+1); err != nil {
                                return
                        }
                } else {
@@ -383,13 +395,13 @@ func (gns *GNSModule) ResolveUnknown(
        return
 }
 
-// GetZoneKey returns the PKEY (or nil) from an absolute GNS path.
-func (gns *GNSModule) GetZoneKey(path string) *ed25519.PublicKey {
+// GetZoneKey returns the zone key (or nil) from an absolute GNS path.
+func (m *Module) GetZoneKey(path string) *crypto.ZoneKey {
        labels := util.ReverseStringList(strings.Split(path, "."))
        if len(labels[0]) == 52 {
                if data, err := util.DecodeStringToBinary(labels[0], 32); err 
== nil {
-                       if pkey := ed25519.NewPublicKeyFromBytes(data); pkey != 
nil {
-                               return pkey
+                       if zkey, err := crypto.NewZoneKey(data); err == nil {
+                               return zkey
                        }
                }
        }
@@ -397,17 +409,17 @@ func (gns *GNSModule) GetZoneKey(path string) 
*ed25519.PublicKey {
 }
 
 // Lookup name in GNS.
-func (gns *GNSModule) Lookup(
+func (m *Module) Lookup(
        ctx *service.SessionContext,
-       pkey *ed25519.PublicKey,
+       zkey *crypto.ZoneKey,
        label string,
-       mode int) (block *message.GNSBlock, err error) {
+       mode int) (block *message.Block, err error) {
 
        // create query (lookup key)
-       query := NewQuery(pkey, label)
+       query := NewQuery(zkey, label)
 
        // try local lookup first
-       if block, err = gns.LookupLocal(ctx, query); err != nil {
+       if block, err = m.LookupLocal(ctx, query); err != nil {
                logger.Printf(logger.ERROR, "[gns] local Lookup: %s\n", 
err.Error())
                block = nil
                return
@@ -415,7 +427,7 @@ func (gns *GNSModule) Lookup(
        if block == nil {
                if mode == enums.GNS_LO_DEFAULT {
                        // get the block from a remote lookup
-                       if block, err = gns.LookupRemote(ctx, query); err != 
nil || block == nil {
+                       if block, err = m.LookupRemote(ctx, query); err != nil 
|| block == nil {
                                if err != nil {
                                        logger.Printf(logger.ERROR, "[gns] 
remote Lookup failed: %s\n", err.Error())
                                        block = nil
@@ -426,15 +438,15 @@ func (gns *GNSModule) Lookup(
                                return
                        }
                        // store RRs from remote locally.
-                       gns.StoreLocal(ctx, block)
+                       m.StoreLocal(ctx, block)
                }
        }
        return
 }
 
 // newLEHORecord creates a new supplemental GNS record of type LEHO.
-func (gns *GNSModule) newLEHORecord(name string, expires util.AbsoluteTime) 
*message.GNSResourceRecord {
-       rr := new(message.GNSResourceRecord)
+func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) 
*message.ResourceRecord {
+       rr := new(message.ResourceRecord)
        rr.Expires = expires
        rr.Flags = uint32(enums.GNS_FLAG_SUPPL)
        rr.Type = uint32(enums.GNS_TYPE_LEHO)
diff --git a/src/gnunet/service/gns/service.go 
b/src/gnunet/service/gns/service.go
index f6310de..6fa1eb2 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -32,31 +32,30 @@ import (
        "gnunet/transport"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
        "github.com/bfix/gospel/logger"
 )
 
 // Error codes
 var (
-       ErrInvalidID           = fmt.Errorf("Invalid/unassociated ID")
-       ErrBlockExpired        = fmt.Errorf("Block expired")
-       ErrInvalidResponseType = fmt.Errorf("Invald response type")
+       ErrInvalidID           = fmt.Errorf("invalid/unassociated ID")
+       ErrBlockExpired        = fmt.Errorf("block expired")
+       ErrInvalidResponseType = fmt.Errorf("invald response type")
 )
 
 //----------------------------------------------------------------------
 // "GNUnet Name System" service implementation
 //----------------------------------------------------------------------
 
-// GNSService
-type GNSService struct {
-       GNSModule
+// Service implements a GNS service
+type Service struct {
+       Module
 }
 
-// NewGNSService
-func NewGNSService() service.Service {
+// NewService creates a new GNS service instance
+func NewService() service.Service {
        // instantiate service and assemble a new GNS handler.
-       inst := new(GNSService)
+       inst := new(Service)
        inst.LookupLocal = inst.LookupNamecache
        inst.StoreLocal = inst.StoreNamecache
        inst.LookupRemote = inst.LookupDHT
@@ -66,67 +65,65 @@ func NewGNSService() service.Service {
 }
 
 // Start the GNS service
-func (s *GNSService) Start(spec string) error {
+func (s *Service) Start(spec string) error {
        return nil
 }
 
 // Stop the GNS service
-func (s *GNSService) Stop() error {
+func (s *Service) Stop() error {
        return nil
 }
 
-// Serve a client channel.
-func (s *GNSService) ServeClient(ctx *service.SessionContext, mc 
*transport.MsgChannel) {
-
-       reqId := 0
+// ServeClient processes a client channel.
+func (s *Service) ServeClient(ctx *service.SessionContext, mc 
*transport.MsgChannel) {
+       reqID := 0
 loop:
        for {
                // receive next message from client
-               reqId++
-               logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client 
request...\n", ctx.Id, reqId)
+               reqID++
+               logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client 
request...\n", ctx.ID, reqID)
                msg, err := mc.Receive(ctx.Signaller())
                if err != nil {
                        if err == io.EOF {
-                               logger.Printf(logger.INFO, "[gns:%d:%d] Client 
channel closed.\n", ctx.Id, reqId)
+                               logger.Printf(logger.INFO, "[gns:%d:%d] Client 
channel closed.\n", ctx.ID, reqID)
                        } else if err == transport.ErrChannelInterrupted {
-                               logger.Printf(logger.INFO, "[gns:%d:%d] Service 
operation interrupted.\n", ctx.Id, reqId)
+                               logger.Printf(logger.INFO, "[gns:%d:%d] Service 
operation interrupted.\n", ctx.ID, reqID)
                        } else {
-                               logger.Printf(logger.ERROR, "[gns:%d:%d] 
Message-receive failed: %s\n", ctx.Id, reqId, err.Error())
+                               logger.Printf(logger.ERROR, "[gns:%d:%d] 
Message-receive failed: %s\n", ctx.ID, reqID, err.Error())
                        }
                        break loop
                }
-               logger.Printf(logger.INFO, "[gns:%d:%d] Received request: 
%v\n", ctx.Id, reqId, msg)
+               logger.Printf(logger.INFO, "[gns:%d:%d] Received request: 
%v\n", ctx.ID, reqID, msg)
 
                // perform lookup
                switch m := msg.(type) {
-               case *message.GNSLookupMsg:
+               case *message.LookupMsg:
                        
//----------------------------------------------------------
                        // GNS_LOOKUP
                        
//----------------------------------------------------------
 
                        // perform lookup on block (locally and remote)
-                       go func(id int, m *message.GNSLookupMsg) {
-                               logger.Printf(logger.INFO, "[gns:%d:%d] Lookup 
request received.\n", ctx.Id, id)
-                               resp := message.NewGNSLookupResultMsg(m.Id)
+                       go func(id int, m *message.LookupMsg) {
+                               logger.Printf(logger.INFO, "[gns:%d:%d] Lookup 
request received.\n", ctx.ID, id)
+                               resp := message.NewGNSLookupResultMsg(m.ID)
                                ctx.Add()
                                defer func() {
                                        // send response
                                        if resp != nil {
                                                if err := mc.Send(resp, 
ctx.Signaller()); err != nil {
-                                                       
logger.Printf(logger.ERROR, "[gns:%d:%d] Failed to send response: %s\n", 
ctx.Id, id, err.Error())
+                                                       
logger.Printf(logger.ERROR, "[gns:%d:%d] Failed to send response: %s\n", 
ctx.ID, id, err.Error())
                                                }
                                        }
                                        // go-routine finished
-                                       logger.Printf(logger.DBG, "[gns:%d:%d] 
Lookup request finished.\n", ctx.Id, id)
+                                       logger.Printf(logger.DBG, "[gns:%d:%d] 
Lookup request finished.\n", ctx.ID, id)
                                        ctx.Remove()
                                }()
 
-                               pkey := ed25519.NewPublicKeyFromBytes(m.Zone)
                                label := m.GetName()
                                kind := NewRRTypeList(int(m.Type))
-                               recset, err := s.Resolve(ctx, label, pkey, 
kind, int(m.Options), 0)
+                               recset, err := s.Resolve(ctx, label, m.Zone, 
kind, int(m.Options), 0)
                                if err != nil {
-                                       logger.Printf(logger.ERROR, 
"[gns:%d:%d] Failed to lookup block: %s\n", ctx.Id, id, err.Error())
+                                       logger.Printf(logger.ERROR, 
"[gns:%d:%d] Failed to lookup block: %s\n", ctx.ID, id, err.Error())
                                        if err == 
transport.ErrChannelInterrupted {
                                                resp = nil
                                        }
@@ -134,16 +131,16 @@ loop:
                                }
                                // handle records
                                if recset != nil {
-                                       logger.Printf(logger.DBG, "[gns:%d:%d] 
Received record set with %d entries\n", ctx.Id, id, recset.Count)
+                                       logger.Printf(logger.DBG, "[gns:%d:%d] 
Received record set with %d entries\n", ctx.ID, id, recset.Count)
 
                                        // get records from block
                                        if recset.Count == 0 {
-                                               logger.Printf(logger.WARN, 
"[gns:%d:%d] No records in block\n", ctx.Id, id)
+                                               logger.Printf(logger.WARN, 
"[gns:%d:%d] No records in block\n", ctx.ID, id)
                                                return
                                        }
                                        // process records
                                        for i, rec := range recset.Records {
-                                               logger.Printf(logger.DBG, 
"[gns:%d:%d] Record #%d: %v\n", ctx.Id, id, i, rec)
+                                               logger.Printf(logger.DBG, 
"[gns:%d:%d] Record #%d: %v\n", ctx.ID, id, i, rec)
 
                                                // is this the record type we 
are looking for?
                                                if rec.Type == m.Type || 
int(m.Type) == enums.GNS_TYPE_ANY {
@@ -152,13 +149,13 @@ loop:
                                                }
                                        }
                                }
-                       }(reqId, m)
+                       }(reqID, m)
 
                default:
                        
//----------------------------------------------------------
                        // UNKNOWN message type received
                        
//----------------------------------------------------------
-                       logger.Printf(logger.ERROR, "[gns:%d:%d] Unhandled 
message of type (%d)\n", ctx.Id, reqId, msg.Header().MsgType)
+                       logger.Printf(logger.ERROR, "[gns:%d:%d] Unhandled 
message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
                        break loop
                }
        }
@@ -166,22 +163,24 @@ loop:
        mc.Close()
 
        // cancel all tasks running for this session/connection
-       logger.Printf(logger.INFO, "[gns:%d] Start closing session... [%d]\n", 
ctx.Id, ctx.Waiting())
+       logger.Printf(logger.INFO, "[gns:%d] Start closing session... [%d]\n", 
ctx.ID, ctx.Waiting())
        ctx.Cancel()
 }
 
+//======================================================================
+// Revocationrelated methods
 //======================================================================
 
-//
-func (s *GNSService) QueryKeyRevocation(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error) {
-       logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", 
util.EncodeBinaryToString(pkey.Bytes()))
+// QueryKeyRevocation checks if a key has been revoked
+func (s *Service) QueryKeyRevocation(ctx *service.SessionContext, zkey 
*crypto.ZoneKey) (valid bool, err error) {
+       logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", 
util.EncodeBinaryToString(zkey.Bytes()))
 
        // assemble request
-       req := message.NewRevocationQueryMsg(pkey)
+       req := message.NewRevocationQueryMsg(zkey)
 
        // get response from Revocation service
        var resp message.Message
-       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
+       if resp, err = service.RequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
                return
        }
 
@@ -195,20 +194,19 @@ func (s *GNSService) QueryKeyRevocation(ctx 
*service.SessionContext, pkey *ed255
        return
 }
 
-//
-func (s *GNSService) RevokeKey(ctx *service.SessionContext, rd 
*revocation.RevData) (success bool, err error) {
-       logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", 
util.EncodeBinaryToString(rd.ZoneKey))
+// RevokeKey revokes a key with given revocation data
+func (s *Service) RevokeKey(ctx *service.SessionContext, rd 
*revocation.RevData) (success bool, err error) {
+       logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", 
rd.ZoneKeySig.ID())
 
        // assemble request
-       req := message.NewRevocationRevokeMsg(nil, nil)
+       req := message.NewRevocationRevokeMsg(nil)
        req.Timestamp = rd.Timestamp
        copy(req.PoWs, rd.PoWs)
-       copy(req.Signature, rd.Signature)
-       copy(req.ZoneKey, rd.ZoneKey)
+       req.ZoneKeySig = rd.ZoneKeySig
 
        // get response from Revocation service
        var resp message.Message
-       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
+       if resp, err = service.RequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
                return
        }
 
@@ -222,20 +220,22 @@ func (s *GNSService) RevokeKey(ctx 
*service.SessionContext, rd *revocation.RevDa
        return
 }
 
+//======================================================================
+// Namecache-related methods
 //======================================================================
 
-// LookupNamecache
-func (s *GNSService) LookupNamecache(ctx *service.SessionContext, query 
*Query) (block *message.GNSBlock, err error) {
+// LookupNamecache returns a cached lookup (if available)
+func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query) 
(block *message.Block, err error) {
        logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", 
hex.EncodeToString(query.Key.Bits))
 
        // assemble Namecache request
        req := message.NewNamecacheLookupMsg(query.Key)
-       req.Id = uint32(util.NextID())
+       req.ID = uint32(util.NextID())
        block = nil
 
        // get response from Namecache service
        var resp message.Message
-       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Namecache", 
config.Cfg.Namecache.Endpoint, req); err != nil {
+       if resp, err = service.RequestResponse(ctx, "gns", "Namecache", 
config.Cfg.Namecache.Endpoint, req); err != nil {
                return
        }
 
@@ -244,7 +244,7 @@ func (s *GNSService) LookupNamecache(ctx 
*service.SessionContext, query *Query)
        switch m := resp.(type) {
        case *message.NamecacheLookupResultMsg:
                // check for matching IDs
-               if m.Id != req.Id {
+               if m.ID != req.ID {
                        logger.Println(logger.ERROR, "[gns] Got response for 
unknown ID")
                        err = ErrInvalidID
                        break
@@ -262,9 +262,8 @@ func (s *GNSService) LookupNamecache(ctx 
*service.SessionContext, query *Query)
                }
 
                // assemble GNSBlock from message
-               block = new(message.GNSBlock)
-               block.Signature = m.Signature
-               block.DerivedKey = m.DerivedKey
+               block = new(message.Block)
+               block.DerivedKeySig = m.DerivedKeySig
                sb := new(message.SignedBlockData)
                sb.Purpose = new(crypto.SignaturePurpose)
                sb.Purpose.Purpose = enums.SIG_GNS_RECORD_SIGN
@@ -287,17 +286,17 @@ func (s *GNSService) LookupNamecache(ctx 
*service.SessionContext, query *Query)
        return
 }
 
-// StoreNamecache
-func (s *GNSService) StoreNamecache(ctx *service.SessionContext, block 
*message.GNSBlock) (err error) {
+// StoreNamecache stores a lookup in the local namecache.
+func (s *Service) StoreNamecache(ctx *service.SessionContext, block 
*message.Block) (err error) {
        logger.Println(logger.DBG, "[gns] StoreNamecache()...")
 
        // assemble Namecache request
        req := message.NewNamecacheCacheMsg(block)
-       req.Id = uint32(util.NextID())
+       req.ID = uint32(util.NextID())
 
        // get response from Namecache service
        var resp message.Message
-       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Namecache", 
config.Cfg.Namecache.Endpoint, req); err != nil {
+       if resp, err = service.RequestResponse(ctx, "gns", "Namecache", 
config.Cfg.Namecache.Endpoint, req); err != nil {
                return
        }
 
@@ -306,7 +305,7 @@ func (s *GNSService) StoreNamecache(ctx 
*service.SessionContext, block *message.
        switch m := resp.(type) {
        case *message.NamecacheCacheResponseMsg:
                // check for matching IDs
-               if m.Id != req.Id {
+               if m.ID != req.ID {
                        logger.Println(logger.ERROR, "[gns] Got response for 
unknown ID")
                        err = ErrInvalidID
                        break
@@ -315,7 +314,7 @@ func (s *GNSService) StoreNamecache(ctx 
*service.SessionContext, block *message.
                if m.Result == 0 {
                        return nil
                }
-               return fmt.Errorf("Failed with rc=%d", m.Result)
+               return fmt.Errorf("failed with rc=%d", m.Result)
        default:
                logger.Printf(logger.ERROR, "[gns] Got invalid response type 
(%d)\n", m.Header().MsgType)
                err = ErrInvalidResponseType
@@ -323,10 +322,12 @@ func (s *GNSService) StoreNamecache(ctx 
*service.SessionContext, block *message.
        return
 }
 
+//======================================================================
+// DHT-related methods
 //======================================================================
 
-// LookupDHT
-func (s *GNSService) LookupDHT(ctx *service.SessionContext, query *Query) 
(block *message.GNSBlock, err error) {
+// LookupDHT gets a GNS block from the DHT for the given query key.
+func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block 
*message.Block, err error) {
        logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", 
hex.EncodeToString(query.Key.Bits))
        block = nil
 
@@ -360,7 +361,7 @@ func (s *GNSService) LookupDHT(ctx *service.SessionContext, 
query *Query) (block
 
        // send DHT GET request and wait for response
        reqGet := message.NewDHTClientGetMsg(query.Key)
-       reqGet.Id = uint64(util.NextID())
+       reqGet.ID = uint64(util.NextID())
        reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
        reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
        reqGet.Options = uint32(enums.DHT_RO_DEMULTIPLEX_EVERYWHERE)
@@ -372,7 +373,7 @@ func (s *GNSService) LookupDHT(ctx *service.SessionContext, 
query *Query) (block
 
                        // send DHT GET_STOP request and terminate
                        reqStop := message.NewDHTClientGetStopMsg(query.Key)
-                       reqStop.Id = reqGet.Id
+                       reqStop.ID = reqGet.ID
                        if err = interact(reqStop, false); err != nil {
                                logger.Printf(logger.ERROR, "[gns] remote 
Lookup abort failed: %s\n", err.Error())
                        }
@@ -385,7 +386,7 @@ func (s *GNSService) LookupDHT(ctx *service.SessionContext, 
query *Query) (block
        switch m := resp.(type) {
        case *message.DHTClientResultMsg:
                // check for matching IDs
-               if m.Id != reqGet.Id {
+               if m.ID != reqGet.ID {
                        logger.Println(logger.ERROR, "[gns] Got response for 
unknown ID")
                        break
                }
@@ -406,7 +407,7 @@ func (s *GNSService) LookupDHT(ctx *service.SessionContext, 
query *Query) (block
                }
 
                // get GNSBlock from message
-               block = message.NewGNSBlock()
+               block = message.NewBlock()
                if err = data.Unmarshal(block, m.Data); err != nil {
                        logger.Printf(logger.ERROR, "[gns] can't read GNS 
block: %s\n", err.Error())
                        break
diff --git a/src/gnunet/service/namecache/module.go 
b/src/gnunet/service/namecache/module.go
index a205444..d5aa014 100644
--- a/src/gnunet/service/namecache/module.go
+++ b/src/gnunet/service/namecache/module.go
@@ -36,10 +36,10 @@ import (
 type NamecacheModule struct {
 }
 
-func (nc *NamecacheModule) Get(ctx *service.SessionContext, query *gns.Query) 
(*message.GNSBlock, error) {
+func (nc *NamecacheModule) Get(ctx *service.SessionContext, query *gns.Query) 
(*message.Block, error) {
        return nil, nil
 }
 
-func (nc *NamecacheModule) Put(ctx *service.SessionContext, block 
*message.GNSBlock) error {
+func (nc *NamecacheModule) Put(ctx *service.SessionContext, block 
*message.Block) error {
        return nil
 }
diff --git a/src/gnunet/service/revocation/module.go 
b/src/gnunet/service/revocation/module.go
index 908cc2e..d13c069 100644
--- a/src/gnunet/service/revocation/module.go
+++ b/src/gnunet/service/revocation/module.go
@@ -20,10 +20,11 @@ package revocation
 
 import (
        "gnunet/config"
+       "gnunet/crypto"
        "gnunet/service"
        "gnunet/util"
+       "net/http"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
        "github.com/bfix/gospel/logger"
 )
@@ -32,14 +33,14 @@ import (
 // "GNUnet Revocation" implementation
 //======================================================================
 
-// RevocationModule handles the revocation-related calls to other modules.
-type RevocationModule struct {
+// Module handles the revocation-related calls to other modules.
+type Module struct {
        bloomf *data.BloomFilter  // bloomfilter for fast revocation check
        kvs    util.KeyValueStore // storage for known revocations
 }
 
 // Init a revocation module
-func (m *RevocationModule) Init() error {
+func (m *Module) Init() error {
        // Initialize access to revocation data storage
        var err error
        if m.kvs, err = util.OpenKVStore(config.Cfg.Revocation.Storage); err != 
nil {
@@ -61,9 +62,9 @@ func (m *RevocationModule) Init() error {
        return nil
 }
 
-// NewRevocationModule returns an initialized revocation module
-func NewRevocationModule() *RevocationModule {
-       m := new(RevocationModule)
+// NewModule returns an initialized revocation module
+func NewModule() *Module {
+       m := new(Module)
        if err := m.Init(); err != nil {
                logger.Printf(logger.ERROR, "[revocation] Failed to initialize 
module: %s\n", err.Error())
                return nil
@@ -71,18 +72,25 @@ func NewRevocationModule() *RevocationModule {
        return m
 }
 
+// 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" }`))
+       }
+}
+
 // Query return true if the pkey is valid (not revoked) and false
 // if the pkey has been revoked.
-func (s *RevocationModule) Query(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error) {
+func (m *Module) Query(ctx *service.SessionContext, zkey *crypto.ZoneKey) 
(valid bool, err error) {
        // fast check first: is the key in the bloomfilter?
-       data := pkey.Bytes()
-       if !s.bloomf.Contains(data) {
+       data := zkey.Bytes()
+       if !m.bloomf.Contains(data) {
                // no: it is valid (not revoked)
                return true, nil
        }
        // check in store to detect false-positives
        key := util.EncodeBinaryToString(data)
-       if _, err = s.kvs.Get(key); err != nil {
+       if _, err = m.kvs.Get(key); err != nil {
                logger.Printf(logger.ERROR, "[revocation] Failed to locate key 
'%s' in store: %s\n", key, err.Error())
                // assume not revoked...
                return true, err
@@ -91,8 +99,8 @@ func (s *RevocationModule) Query(ctx *service.SessionContext, 
pkey *ed25519.Publ
        return false, nil
 }
 
-// Revoke
-func (s *RevocationModule) Revoke(ctx *service.SessionContext, rd *RevData) 
(success bool, err error) {
+// Revoke a key with given revocation data
+func (m *Module) Revoke(ctx *service.SessionContext, rd *RevData) (success 
bool, err error) {
        // verify the revocation data
        rc := rd.Verify(true)
        switch {
@@ -111,14 +119,13 @@ func (s *RevocationModule) Revoke(ctx 
*service.SessionContext, rd *RevData) (suc
        }
        // store the revocation data
        // (1) add it to the bloomfilter
-       s.bloomf.Add(rd.ZoneKey)
+       m.bloomf.Add(rd.ZoneKeySig.KeyData)
        // (2) add it to the store
        var buf []byte
-       key := util.EncodeBinaryToString(rd.ZoneKey)
        if buf, err = data.Marshal(rd); err != nil {
                return false, err
        }
        value := util.EncodeBinaryToString(buf)
-       err = s.kvs.Put(key, value)
+       err = m.kvs.Put(rd.ZoneKeySig.ID(), value)
        return true, err
 }
diff --git a/src/gnunet/service/revocation/pow.go 
b/src/gnunet/service/revocation/pow.go
index dd702b4..04bbb13 100644
--- a/src/gnunet/service/revocation/pow.go
+++ b/src/gnunet/service/revocation/pow.go
@@ -31,7 +31,6 @@ import (
        "gnunet/message"
        "gnunet/util"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
        "github.com/bfix/gospel/math"
        "golang.org/x/crypto/argon2"
@@ -44,15 +43,15 @@ import (
 // PoWData is the proof-of-work data
 type PoWData struct {
        PoW       uint64            `order:"big"` // start with this PoW value
-       Timestamp util.AbsoluteTime // Timestamp of creation
-       ZoneKey   []byte            `size:"32"` // public zone key to be revoked
+       Timestamp util.AbsoluteTime ``            // Timestamp of creation
+       ZoneKey   *crypto.ZoneKey   ``            // public zone key to be 
revoked
 
        // transient attributes (not serialized)
        blob []byte // binary representation of serialized data
 }
 
 // NewPoWData creates a PoWData instance for the given arguments.
-func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey []byte) *PoWData {
+func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey *crypto.ZoneKey) 
*PoWData {
        rd := &PoWData{
                PoW:       0,
                Timestamp: ts,
@@ -69,7 +68,7 @@ func (p *PoWData) SetPoW(pow uint64) error {
        p.PoW = pow
        p.blob = p.Blob()
        if p.blob == nil {
-               return fmt.Errorf("Invalid PoW work unit")
+               return fmt.Errorf("invalid PoW work unit")
        }
        return nil
 }
@@ -119,26 +118,24 @@ func (p *PoWData) Blob() []byte {
 
 // RevData is the revocation data (wire format)
 type RevData struct {
-       Timestamp util.AbsoluteTime // Timestamp of creation
-       TTL       util.RelativeTime // TTL of revocation
-       PoWs      []uint64          `size:"32" order:"big"` // (Sorted) list of 
PoW values
-       Signature []byte            `size:"64"`             // Signature 
(Proof-of-ownership).
-       ZoneKey   []byte            `size:"32"`             // public zone key 
to be revoked
+       Timestamp  util.AbsoluteTime     ``                      // Timestamp 
of creation
+       TTL        util.RelativeTime     ``                      // TTL of 
revocation
+       PoWs       []uint64              `size:"32" order:"big"` // (Sorted) 
list of PoW values
+       ZoneKeySig *crypto.ZoneSignature ``                      // public zone 
key to be revoked
 }
 
 // SignedRevData is the block of data signed for a RevData instance.
 type SignedRevData struct {
-       Purpose   *crypto.SignaturePurpose
-       ZoneKey   []byte            `size:"32"` // public zone key to be revoked
-       Timestamp util.AbsoluteTime // Timestamp of creation
+       Purpose   *crypto.SignaturePurpose // signature purpose
+       Timestamp util.AbsoluteTime        // Timestamp of creation
+       ZoneKey   *crypto.ZoneKey          // public zone key to be revoked
 }
 
 // NewRevDataFromMsg initializes a new RevData instance from a GNUnet message
 func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData {
        rd := &RevData{
-               Timestamp: m.Timestamp,
-               Signature: util.Clone(m.Signature),
-               ZoneKey:   util.Clone(m.ZoneKey),
+               Timestamp:  m.Timestamp,
+               ZoneKeySig: m.ZoneKeySig,
        }
        for i, pow := range m.PoWs {
                rd.PoWs[i] = pow
@@ -147,25 +144,20 @@ func NewRevDataFromMsg(m *message.RevocationRevokeMsg) 
*RevData {
 }
 
 // Sign the revocation data
-func (rd *RevData) Sign(skey *ed25519.PrivateKey) error {
+func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err error) {
        sigBlock := &SignedRevData{
                Purpose: &crypto.SignaturePurpose{
-                       Size:    48,
+                       Size:    uint32(20 + rd.ZoneKeySig.KeySize()),
                        Purpose: enums.SIG_REVOCATION,
                },
-               ZoneKey:   util.Clone(rd.ZoneKey),
                Timestamp: rd.Timestamp,
+               ZoneKey:   &rd.ZoneKeySig.ZoneKey,
        }
        sigData, err := data.Marshal(sigBlock)
-       if err != nil {
-               return err
-       }
-       sig, err := skey.EcSign(sigData)
-       if err != nil {
-               return err
+       if err == nil {
+               rd.ZoneKeySig, err = skey.Sign(sigData)
        }
-       copy(rd.Signature, sig.Bytes())
-       return nil
+       return
 }
 
 // Verify a revocation object: returns the (smallest) number of leading
@@ -179,22 +171,17 @@ func (rd *RevData) Verify(withSig bool) int {
        if withSig {
                sigBlock := &SignedRevData{
                        Purpose: &crypto.SignaturePurpose{
-                               Size:    48,
+                               Size:    uint32(20 + rd.ZoneKeySig.KeySize()),
                                Purpose: enums.SIG_REVOCATION,
                        },
-                       ZoneKey:   util.Clone(rd.ZoneKey),
                        Timestamp: rd.Timestamp,
+                       ZoneKey:   &rd.ZoneKeySig.ZoneKey,
                }
                sigData, err := data.Marshal(sigBlock)
                if err != nil {
                        return -1
                }
-               pkey := ed25519.NewPublicKeyFromBytes(rd.ZoneKey)
-               sig, err := ed25519.NewEcSignatureFromBytes(rd.Signature)
-               if err != nil {
-                       return -1
-               }
-               valid, err := pkey.EcVerify(sigData, sig)
+               valid, err := rd.ZoneKeySig.Verify(sigData)
                if err != nil || !valid {
                        return -1
                }
@@ -212,7 +199,7 @@ func (rd *RevData) Verify(withSig bool) int {
                }
                last = pow
                // compute number of leading zero-bits
-               work := NewPoWData(pow, rd.Timestamp, rd.ZoneKey)
+               work := NewPoWData(pow, rd.Timestamp, &rd.ZoneKeySig.ZoneKey)
                zbits += float64(512 - work.Compute().BitLen())
        }
        zbits /= 32.0
@@ -240,18 +227,16 @@ type RevDataCalc struct {
 }
 
 // NewRevDataCalc initializes a new RevDataCalc instance
-func NewRevDataCalc(pkey []byte) *RevDataCalc {
+func NewRevDataCalc(zkey *crypto.ZoneKey) *RevDataCalc {
        rd := &RevDataCalc{
                RevData: RevData{
-                       Timestamp: util.AbsoluteTimeNow(),
-                       PoWs:      make([]uint64, 32),
-                       Signature: make([]byte, 64),
-                       ZoneKey:   make([]byte, 32),
+                       Timestamp:  util.AbsoluteTimeNow(),
+                       PoWs:       make([]uint64, 32),
+                       ZoneKeySig: nil,
                },
                Bits:        make([]uint16, 32),
                SmallestIdx: 0,
        }
-       copy(rd.ZoneKey, pkey)
        return rd
 }
 
@@ -294,7 +279,7 @@ func (rdc *RevDataCalc) sortBits() {
 // complete if the average above is greater or equal to 'bits'.
 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.ZoneKey)
+       work := NewPoWData(0, rdc.Timestamp, &rdc.ZoneKeySig.ZoneKey)
        var max uint64 = 0
        for i, pow := range rdc.PoWs {
                if pow == 0 {
diff --git a/src/gnunet/service/revocation/pow_test.go 
b/src/gnunet/service/revocation/pow_test.go
index 6e7a355..8280402 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -3,130 +3,147 @@ package revocation
 import (
        "bytes"
        "encoding/hex"
-       "fmt"
+       "gnunet/crypto"
+       "gnunet/enums"
        "testing"
 
-       "gnunet/util"
-
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
-       "github.com/bfix/gospel/math"
 )
 
-type testData struct {
-       skey    string
-       pkey    string
-       revdata string
-}
+// Test revocation with test vector defined in the RFC draft.
+func TestRevocationRFC(t *testing.T) {
 
-var (
-       test_data = []testData{
-               {
+       var (
+               D     = 
"6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
+               ZKEY  = 
"000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
+               DIFF  = 7
+               PROOF = "" +
+                       "0005d66da3598127" +
+                       "0000395d1827c000" +
+                       "3ab877d07570f2b8" +
+                       "3ab877d07570f332" +
+                       "3ab877d07570f4f5" +
+                       "3ab877d07570f50f" +
+                       "3ab877d07570f537" +
+                       "3ab877d07570f599" +
+                       "3ab877d07570f5cd" +
+                       "3ab877d07570f5d9" +
+                       "3ab877d07570f66a" +
+                       "3ab877d07570f69b" +
+                       "3ab877d07570f72f" +
+                       "3ab877d07570f7c3" +
+                       "3ab877d07570f843" +
+                       "3ab877d07570f8d8" +
+                       "3ab877d07570f91b" +
+                       "3ab877d07570f93a" +
+                       "3ab877d07570f944" +
+                       "3ab877d07570f98a" +
+                       "3ab877d07570f9a7" +
+                       "3ab877d07570f9b0" +
+                       "3ab877d07570f9df" +
+                       "3ab877d07570fa05" +
+                       "3ab877d07570fa3e" +
+                       "3ab877d07570fa63" +
+                       "3ab877d07570fa84" +
+                       "3ab877d07570fa8f" +
+                       "3ab877d07570fa91" +
+                       "3ab877d07570fad6" +
+                       "3ab877d07570fb0a" +
+                       "3ab877d07570fc0f" +
+                       "3ab877d07570fc43" +
+                       "3ab877d07570fca5" +
+                       "00010000" +
+                       
"2ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa" +
+                       
"053b0259700039187d1da4613531502bc4a4eeccc69900d24f8aac5430f28fc5092701331f178e290fe06e82ce2498ce7b23a34058e3d6a2f247e92bc9d7b9ab"
+       )
 
-                       
"90ea2a95cb9ef482b45817dc45b805cae00f387022a065a3674f41ad15173c63", // private 
scalar D
-                       
"4ac1e51d9a585a9ad9fb0dfac2be100aee83f0cc79c4c5ea8f3eb8afd9092fa5", // public 
key
-                       "" +
-                               "0005a5fd368978f4" + // private scalar D
-                               "0000395d1827c000" + // public key Ed25519
-                               "e23f657bc47ec853" + // PoW_0
-                               "e23f657bc47ec9d8" +
-                               "e23f657bc47ecaec" +
-                               "e23f657bc47ecb29" +
-                               "e23f657bc47ecc00" +
-                               "e23f657bc47ecc79" +
-                               "e23f657bc47ece83" +
-                               "e23f657bc47ecfc6" +
-                               "e23f657bc47ecfc8" +
-                               "e23f657bc47ecfd5" +
-                               "e23f657bc47ed02b" +
-                               "e23f657bc47ed03b" +
-                               "e23f657bc47ed0ff" +
-                               "e23f657bc47ed241" +
-                               "e23f657bc47ed264" +
-                               "e23f657bc47ed2e5" +
-                               "e23f657bc47ed343" +
-                               "e23f657bc47ed348" +
-                               "e23f657bc47ed45e" +
-                               "e23f657bc47ed480" +
-                               "e23f657bc47ed49a" +
-                               "e23f657bc47ed564" +
-                               "e23f657bc47ed565" +
-                               "e23f657bc47ed5b6" +
-                               "e23f657bc47ed5de" +
-                               "e23f657bc47ed5e0" +
-                               "e23f657bc47ed77f" +
-                               "e23f657bc47ed800" +
-                               "e23f657bc47ed80c" +
-                               "e23f657bc47ed817" +
-                               "e23f657bc47ed82c" +
-                               "e23f657bc47ed8a6" + // PoW_31
-                               
"0396020c831a5405cee6c38842209191c8db799dbe81e0dcf6dbd4f91c257ae2" + // Sig.R
-                               
"0079e7fd1cd31cc24cd9a52831d5ec30f10e22e5a6dd906518746cfce2095610" + // Sig.S
-                               
"4ac1e51d9a585a9ad9fb0dfac2be100aee83f0cc79c4c5ea8f3eb8afd9092fa5", // PKEY
-               },
+       // construct private/public key pair from test data
+       d, err := hex.DecodeString(D)
+       if err != nil {
+               t.Fatal(err)
        }
-)
+       prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, d)
+       if err != nil {
+               t.Fatal(err)
+       }
+       zk := prv.Public()
 
-func TestRevocationRFC(t *testing.T) {
+       // check
+       zkey, err := hex.DecodeString(ZKEY)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(zk.Bytes(), zkey) {
+               t.Logf("zkey = %s\n", hex.EncodeToString(zk.Bytes()))
+               t.Logf("ZKEY = %s\n", hex.EncodeToString(zkey))
+               t.Fatal("Private/Public key mismatch")
+       }
 
-       for i, td := range test_data {
-               if testing.Verbose() {
-                       fmt.Println("---------------------------------")
-                       fmt.Printf("Test case #%d\n", i+1)
-                       fmt.Println("---------------------------------")
-               }
+       // assemble revocation data object
+       revD, err := hex.DecodeString(PROOF)
+       if err != nil {
+               t.Fatal(err)
+       }
+       revData := new(RevData)
+       if err = data.Unmarshal(revData, revD); err != nil {
+               t.Fatal(err)
+       }
+       if !bytes.Equal(revData.ZoneKeySig.Bytes(), zkey) {
+               t.Logf("zkey  = %s\n", 
hex.EncodeToString(revData.ZoneKeySig.Bytes()))
+               t.Logf("ZKEY  = %s\n", hex.EncodeToString(zkey))
+               t.Fatal("Wrong zone key in test revocation")
+       }
 
-               // construct private/public key pair from test data
-               skey_d, err := hex.DecodeString(td.skey)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               d := math.NewIntFromBytes(util.Reverse(skey_d))
-               skey := ed25519.NewPrivateKeyFromD(d)
-               pkey_d, err := hex.DecodeString(td.pkey)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               if bytes.Compare(skey.Public().Bytes(), pkey_d) != 0 {
-                       t.Fatal("Private/Public key mismatch")
-               }
+       // show revdata content
+       if testing.Verbose() {
+               t.Log("REVDATA:")
+               t.Logf("    Timestamp: %s\n", revData.Timestamp.String())
+               t.Logf("    TTL: %s\n", revData.TTL.String())
 
-               // assemble revocation data object
-               rev_d, err := hex.DecodeString(td.revdata)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               revData := new(RevData)
-               if err = data.Unmarshal(revData, rev_d); err != nil {
-                       t.Fatal(err)
-               }
-               if bytes.Compare(revData.ZoneKey, pkey_d) != 0 {
-                       t.Fatal("Wrong zone key in test revocation")
+               work := NewPoWData(0, revData.Timestamp, 
&revData.ZoneKeySig.ZoneKey)
+               for i, pow := range revData.PoWs {
+                       t.Logf("    PoW #%d: %d\n", i, pow)
+                       work.SetPoW(pow)
+                       buf := work.Blob()
+                       t.Logf("        P: %s\n", hex.EncodeToString(buf))
+                       v := work.Compute()
+                       t.Logf("        H: %s\n", hex.EncodeToString(v.Bytes()))
+                       num := 512 - v.BitLen()
+                       t.Logf("        --> %d leading zeros\n", num)
                }
+               t.Logf("    ZoneKey: %s\n", 
hex.EncodeToString(revData.ZoneKeySig.KeyData))
+               t.Logf("    Signature: %s\n", 
hex.EncodeToString(revData.ZoneKeySig.Signature))
+       }
 
-               // show revdata content
-               if testing.Verbose() {
-                       fmt.Println("REVDATA:")
-                       fmt.Printf("    Timestamp: %s\n", 
revData.Timestamp.String())
-                       fmt.Printf("    TTL: %s\n", revData.TTL.String())
+       // assemble data for signature
+       sigBlock := &SignedRevData{
+               Purpose: &crypto.SignaturePurpose{
+                       Size:    uint32(20 + revData.ZoneKeySig.KeySize()),
+                       Purpose: enums.SIG_REVOCATION,
+               },
+               Timestamp: revData.Timestamp,
+               ZoneKey:   &revData.ZoneKeySig.ZoneKey,
+       }
+       sigData, err := data.Marshal(sigBlock)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if testing.Verbose() {
+               t.Logf("SigData = %s\n", hex.EncodeToString(sigData))
+       }
 
-                       work := NewPoWData(0, revData.Timestamp, 
revData.ZoneKey)
-                       for i, pow := range revData.PoWs {
-                               fmt.Printf("    PoW #%d: %d\n", i, pow)
-                               work.SetPoW(pow)
-                               buf := work.Blob()
-                               fmt.Printf("        P: %s\n", 
hex.EncodeToString(buf))
-                               v := work.Compute()
-                               fmt.Printf("        H: %s\n", 
hex.EncodeToString(v.Bytes()))
-                               num := 512 - v.BitLen()
-                               fmt.Printf("        --> %d leading zeros\n", 
num)
-                       }
-                       fmt.Printf("    Signature: %s\n", 
hex.EncodeToString(revData.Signature))
-                       fmt.Printf("    ZoneKey: %s\n", 
hex.EncodeToString(revData.ZoneKey))
-               }
+       sigOut, err := prv.Sign(sigData)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if testing.Verbose() {
+               t.Logf("Signature = %s\n", hex.EncodeToString(sigOut.Signature))
+               t.Logf("         ?= %s\n", 
hex.EncodeToString(revData.ZoneKeySig.Signature))
+       }
 
-               // verify revocation data object
-               rc := revData.Verify(true)
-               fmt.Printf("REV_Verify (pkey): %d\n", rc)
+       // verify revocation data object
+       rc := revData.Verify(true)
+       if rc != DIFF {
+               t.Fatalf("REV_Verify (pkey): %d\n", rc)
        }
 }
diff --git a/src/gnunet/service/revocation/service.go 
b/src/gnunet/service/revocation/service.go
index f2a6ec3..a82e582 100644
--- a/src/gnunet/service/revocation/service.go
+++ b/src/gnunet/service/revocation/service.go
@@ -25,7 +25,6 @@ import (
        "gnunet/service"
        "gnunet/transport"
 
-       "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/logger"
 )
 
@@ -33,49 +32,48 @@ import (
 // "GNUnet Revocation" service implementation
 //----------------------------------------------------------------------
 
-// RevocationService
-type RevocationService struct {
-       RevocationModule
+// Service implements a revocation service
+type Service struct {
+       Module
 }
 
-// NewRevocationService
-func NewRevocationService() service.Service {
+// NewService creates a new revocation service instance
+func NewService() service.Service {
        // instantiate service and assemble a new Revocation handler.
-       inst := new(RevocationService)
+       inst := new(Service)
        return inst
 }
 
 // Start the Revocation service
-func (s *RevocationService) Start(spec string) error {
+func (s *Service) Start(spec string) error {
        return nil
 }
 
 // Stop the Revocation service
-func (s *RevocationService) Stop() error {
+func (s *Service) Stop() error {
        return nil
 }
 
-// Serve a client channel.
-func (s *RevocationService) ServeClient(ctx *service.SessionContext, mc 
*transport.MsgChannel) {
-
-       reqId := 0
+// ServeClient processes a client channel.
+func (s *Service) ServeClient(ctx *service.SessionContext, mc 
*transport.MsgChannel) {
+       reqID := 0
 loop:
        for {
                // receive next message from client
-               reqId++
-               logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for 
client request...\n", ctx.Id, reqId)
+               reqID++
+               logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for 
client request...\n", ctx.ID, reqID)
                msg, err := mc.Receive(ctx.Signaller())
                if err != nil {
                        if err == io.EOF {
-                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Client channel closed.\n", ctx.Id, reqId)
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Client channel closed.\n", ctx.ID, reqID)
                        } else if err == transport.ErrChannelInterrupted {
-                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Service operation interrupted.\n", ctx.Id, reqId)
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Service operation interrupted.\n", ctx.ID, reqID)
                        } else {
-                               logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Message-receive failed: %s\n", ctx.Id, reqId, err.Error())
+                               logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Message-receive failed: %s\n", ctx.ID, reqID, err.Error())
                        }
                        break loop
                }
-               logger.Printf(logger.INFO, "[revocation:%d:%d] Received 
request: %v\n", ctx.Id, reqId, msg)
+               logger.Printf(logger.INFO, "[revocation:%d:%d] Received 
request: %v\n", ctx.ID, reqID, msg)
 
                // handle request
                switch m := msg.(type) {
@@ -84,70 +82,69 @@ loop:
                        // REVOCATION_QUERY
                        
//----------------------------------------------------------
                        go func(id int, m *message.RevocationQueryMsg) {
-                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Query request received.\n", ctx.Id, id)
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Query request received.\n", ctx.ID, id)
                                var resp *message.RevocationQueryResponseMsg
                                ctx.Add()
                                defer func() {
                                        // send response
                                        if resp != nil {
                                                if err := mc.Send(resp, 
ctx.Signaller()); err != nil {
-                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.Id, id, err.Error())
+                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.ID, id, err.Error())
                                                }
                                        }
                                        // go-routine finished
-                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Query request finished.\n", ctx.Id, id)
+                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Query request finished.\n", ctx.ID, id)
                                        ctx.Remove()
                                }()
 
-                               pkey := ed25519.NewPublicKeyFromBytes(m.Zone)
-                               valid, err := s.Query(ctx, pkey)
+                               valid, err := s.Query(ctx, m.Zone)
                                if err != nil {
-                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.Id, id, 
err.Error())
+                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.ID, id, 
err.Error())
                                        if err == 
transport.ErrChannelInterrupted {
                                                resp = nil
                                        }
                                        return
                                }
                                resp = 
message.NewRevocationQueryResponseMsg(valid)
-                       }(reqId, m)
+                       }(reqID, m)
 
                case *message.RevocationRevokeMsg:
                        
//----------------------------------------------------------
                        // REVOCATION_REVOKE
                        
//----------------------------------------------------------
                        go func(id int, m *message.RevocationRevokeMsg) {
-                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Revoke request received.\n", ctx.Id, id)
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Revoke request received.\n", ctx.ID, id)
                                var resp *message.RevocationRevokeResponseMsg
                                ctx.Add()
                                defer func() {
                                        // send response
                                        if resp != nil {
                                                if err := mc.Send(resp, 
ctx.Signaller()); err != nil {
-                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.Id, id, err.Error())
+                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.ID, id, err.Error())
                                                }
                                        }
                                        // go-routine finished
-                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Revoke request finished.\n", ctx.Id, id)
+                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Revoke request finished.\n", ctx.ID, id)
                                        ctx.Remove()
                                }()
 
                                rd := NewRevDataFromMsg(m)
                                valid, err := s.Revoke(ctx, rd)
                                if err != nil {
-                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to revoke key: %s\n", ctx.Id, id, err.Error())
+                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to revoke key: %s\n", ctx.ID, id, err.Error())
                                        if err == 
transport.ErrChannelInterrupted {
                                                resp = nil
                                        }
                                        return
                                }
                                resp = 
message.NewRevocationRevokeResponseMsg(valid)
-                       }(reqId, m)
+                       }(reqID, m)
 
                default:
                        
//----------------------------------------------------------
                        // UNKNOWN message type received
                        
//----------------------------------------------------------
-                       logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Unhandled message of type (%d)\n", ctx.Id, reqId, msg.Header().MsgType)
+                       logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Unhandled message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
                        break loop
                }
        }
@@ -155,6 +152,6 @@ loop:
        mc.Close()
 
        // cancel all tasks running for this session/connection
-       logger.Printf(logger.INFO, "[revocation:%d] Start closing session... 
[%d]\n", ctx.Id, ctx.Waiting())
+       logger.Printf(logger.INFO, "[revocation:%d] Start closing session... 
[%d]\n", ctx.ID, ctx.Waiting())
        ctx.Cancel()
 }
diff --git a/src/gnunet/service/service.go b/src/gnunet/service/service.go
index 1017e0b..c996e0c 100644
--- a/src/gnunet/service/service.go
+++ b/src/gnunet/service/service.go
@@ -20,6 +20,7 @@ package service
 
 import (
        "fmt"
+       "net/http"
        "sync"
 
        "gnunet/transport"
@@ -27,18 +28,28 @@ import (
        "github.com/bfix/gospel/logger"
 )
 
+// Module is an interface for GNUnet service modules (workers).
+type Module interface {
+       // RPC returns the route and handler for JSON-RPC requests
+       RPC() (string, func(http.ResponseWriter, *http.Request))
+}
+
 // Service is an interface for GNUnet services. Every service has one channel
 // end-point it listens to for incoming channel requests (network-based
 // channels established by service clients). The end-point is specified in
 // Channel semantics in the specification string.
 type Service interface {
+       Module
+       // Start a service on the given endpoint
        Start(spec string) error
+       // Serve a client session
        ServeClient(ctx *SessionContext, ch *transport.MsgChannel)
+       // Stop the service
        Stop() error
 }
 
-// ServiceImpl is an implementation of generic service functionality.
-type ServiceImpl struct {
+// Impl is an implementation of generic service functionality.
+type Impl struct {
        impl    Service                 // Specific service implementation
        hdlr    chan transport.Channel  // Channel from listener
        ctrl    chan bool               // Control channel
@@ -51,8 +62,8 @@ type ServiceImpl struct {
 }
 
 // NewServiceImpl instantiates a new ServiceImpl object.
-func NewServiceImpl(name string, srv Service) *ServiceImpl {
-       return &ServiceImpl{
+func NewServiceImpl(name string, srv Service) *Impl {
+       return &Impl{
                impl:    srv,
                hdlr:    make(chan transport.Channel),
                ctrl:    make(chan bool),
@@ -66,7 +77,7 @@ func NewServiceImpl(name string, srv Service) *ServiceImpl {
 }
 
 // Start a service
-func (si *ServiceImpl) Start(spec string) (err error) {
+func (si *Impl) Start(spec string) (err error) {
        // check if we are already running
        if si.running {
                logger.Printf(logger.ERROR, "Service '%s' already running.\n", 
si.name)
@@ -98,22 +109,22 @@ func (si *ServiceImpl) Start(spec string) (err error) {
                                case transport.Channel:
                                        // run a new session with context
                                        ctx := NewSessionContext()
-                                       sessId := ctx.Id
-                                       si.pending[sessId] = ctx
-                                       logger.Printf(logger.INFO, "[%s] 
Session '%d' started.\n", si.name, sessId)
+                                       sessID := ctx.ID
+                                       si.pending[sessID] = ctx
+                                       logger.Printf(logger.INFO, "[%s] 
Session '%d' started.\n", si.name, sessID)
 
                                        go func() {
                                                // serve client on the message 
channel
                                                si.impl.ServeClient(ctx, 
transport.NewMsgChannel(ch))
                                                // session is done now.
-                                               logger.Printf(logger.INFO, 
"[%s] Session with client '%d' ended.\n", si.name, sessId)
-                                               si.drop <- sessId
+                                               logger.Printf(logger.INFO, 
"[%s] Session with client '%d' ended.\n", si.name, sessID)
+                                               si.drop <- sessID
                                        }()
                                }
 
                        // handle session removal
-                       case sessId := <-si.drop:
-                               delete(si.pending, sessId)
+                       case sessID := <-si.drop:
+                               delete(si.pending, sessID)
 
                        // handle cancelation signal on listener.
                        case <-si.ctrl:
@@ -123,7 +134,7 @@ func (si *ServiceImpl) Start(spec string) (err error) {
 
                // terminate pending sessions
                for _, ctx := range si.pending {
-                       logger.Printf(logger.DBG, "[%s] Session '%d' 
closing...\n", si.name, ctx.Id)
+                       logger.Printf(logger.DBG, "[%s] Session '%d' 
closing...\n", si.name, ctx.ID)
                        ctx.Cancel()
                }
 
@@ -137,7 +148,7 @@ func (si *ServiceImpl) Start(spec string) (err error) {
 }
 
 // Stop a service
-func (si *ServiceImpl) Stop() error {
+func (si *Impl) Stop() error {
        if !si.running {
                logger.Printf(logger.WARN, "Service '%s' not running.\n", 
si.name)
                return fmt.Errorf("service not running")
diff --git a/src/gnunet/transport/channel.go b/src/gnunet/transport/channel.go
index 458a063..1632aab 100644
--- a/src/gnunet/transport/channel.go
+++ b/src/gnunet/transport/channel.go
@@ -35,9 +35,9 @@ import (
 
 // Error codes
 var (
-       ErrChannelNotImplemented = fmt.Errorf("Protocol not implemented")
-       ErrChannelNotOpened      = fmt.Errorf("Channel not opened")
-       ErrChannelInterrupted    = fmt.Errorf("Channel interrupted")
+       ErrChannelNotImplemented = fmt.Errorf("protocol not implemented")
+       ErrChannelNotOpened      = fmt.Errorf("channel not opened")
+       ErrChannelInterrupted    = fmt.Errorf("channel interrupted")
 )
 
 ////////////////////////////////////////////////////////////////////////
@@ -102,7 +102,7 @@ var channelServerImpl = map[string]ChannelServerFactory{
        "udp":  NewUDPChannelServer,
 }
 
-// NewChannelServer
+// NewChannelServer creates a new channel server instance
 func NewChannelServer(spec string, hdlr chan<- Channel) (cs ChannelServer, err 
error) {
        parts := strings.Split(spec, "+")
 
@@ -158,7 +158,7 @@ func (c *MsgChannel) Send(msg message.Message, sig 
*concurrent.Signaller) error
                return err
        }
        if len(data) != int(mh.MsgSize) {
-               return errors.New("Send: message size mismatch")
+               return errors.New("send: message size mismatch")
        }
 
        // send packet
@@ -167,7 +167,7 @@ func (c *MsgChannel) Send(msg message.Message, sig 
*concurrent.Signaller) error
                return err
        }
        if n != len(data) {
-               return errors.New("Incomplete send")
+               return errors.New("incomplete send")
        }
        return nil
 }
@@ -202,7 +202,7 @@ func (c *MsgChannel) Receive(sig *concurrent.Signaller) 
(message.Message, error)
                return nil, err
        }
        if msg == nil {
-               return nil, fmt.Errorf("Message{%d} is nil!\n", mh.MsgType)
+               return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
        }
        if err = data.Unmarshal(msg, c.buf[:mh.MsgSize]); err != nil {
                return nil, err
diff --git a/src/gnunet/transport/channel_netw.go 
b/src/gnunet/transport/channel_netw.go
index b70faa4..c0de978 100644
--- a/src/gnunet/transport/channel_netw.go
+++ b/src/gnunet/transport/channel_netw.go
@@ -42,7 +42,7 @@ func NewChannelResult(n int, err error) *ChannelResult {
        }
 }
 
-// Values() returns the attributes of a result instance (for passing up the
+// Values returns the attributes of a result instance (for passing up the
 // call stack).
 func (cr *ChannelResult) Values() (int, error) {
        return cr.count, cr.err
@@ -51,7 +51,7 @@ func (cr *ChannelResult) Values() (int, error) {
 ////////////////////////////////////////////////////////////////////////
 // Generic network-based Channel
 
-// NetworkChannel
+// NetworkChannel represents a network-based channel
 type NetworkChannel struct {
        network string   // network protocol identifier ("tcp", "unix", ...)
        conn    net.Conn // associated connection
@@ -167,13 +167,13 @@ func (c *NetworkChannel) Write(buf []byte, sig 
*concurrent.Signaller) (int, erro
 ////////////////////////////////////////////////////////////////////////
 // Generic network-based ChannelServer
 
-// NetworkChannelServer
+// NetworkChannelServer represents a network-based channel server
 type NetworkChannelServer struct {
        network  string       // network protocol to listen on
        listener net.Listener // reference to listener object
 }
 
-// NewNetworkChannelServer
+// NewNetworkChannelServer creates a new network-based channel server
 func NewNetworkChannelServer(netw string) ChannelServer {
        return &NetworkChannelServer{
                network:  netw,
@@ -254,32 +254,32 @@ func (s *NetworkChannelServer) Close() error {
 // helper functions to instantiate network channels and servers for
 // common network protocols
 
-// NewSocketChannel: Unix Domain Socket connection
+// NewSocketChannel implements a Unix Domain Socket connection
 func NewSocketChannel() Channel {
        return NewNetworkChannel("unix")
 }
 
-// NewTCPChannel: TCP connection
+// NewTCPChannel implements a: TCP connection
 func NewTCPChannel() Channel {
        return NewNetworkChannel("tcp")
 }
 
-// NewUDPChannel: UDP connection
+// NewUDPChannel implements an UDP connection
 func NewUDPChannel() Channel {
        return NewNetworkChannel("udp")
 }
 
-// NewSocketChannelServer: Unix Domain Socket listener
+// NewSocketChannelServer implements an Unix Domain Socket listener
 func NewSocketChannelServer() ChannelServer {
        return NewNetworkChannelServer("unix")
 }
 
-// NewTCPChannelServer: TCP listener
+// NewTCPChannelServer implements a TCP listener
 func NewTCPChannelServer() ChannelServer {
        return NewNetworkChannelServer("tcp")
 }
 
-// NewUDPChannelServer: UDP listener
+// NewUDPChannelServer implements an UDP listener
 func NewUDPChannelServer() ChannelServer {
        return NewNetworkChannelServer("udp")
 }
diff --git a/src/gnunet/transport/channel_test.go 
b/src/gnunet/transport/channel_test.go
index 41831b3..f028171 100644
--- a/src/gnunet/transport/channel_test.go
+++ b/src/gnunet/transport/channel_test.go
@@ -23,12 +23,17 @@ import (
        "fmt"
        "testing"
        "time"
+
+       "github.com/bfix/gospel/concurrent"
 )
 
+// TODO: These test cases fail from time to time for no obvious reason.
+// This needs to be investigated.
+
 const (
-       SOCK_ADDR       = "/tmp/gnunet-go-test.sock"
-       TCP_ADDR_CLIENT = "gnunet.org:80"
-       TCP_ADDR_SERVER = "127.0.0.1:12086"
+       SockAddr      = "/tmp/gnunet-go-test.sock"
+       TCPAddrClient = "gnunet.org:80"
+       TCPAddrServer = "127.0.0.1:9876"
 )
 
 type TestChannelServer struct {
@@ -45,14 +50,14 @@ func NewTestChannelServer() *TestChannelServer {
        }
 }
 
-func (s *TestChannelServer) handle(ch Channel) {
+func (s *TestChannelServer) handle(ch Channel, sig *concurrent.Signaller) {
        buf := make([]byte, 4096)
        for {
-               n, err := ch.Read(buf)
+               n, err := ch.Read(buf, sig)
                if err != nil {
                        break
                }
-               _, err = ch.Write(buf[:n])
+               _, err = ch.Write(buf[:n], sig)
                if err != nil {
                        break
                }
@@ -73,17 +78,16 @@ func (s *TestChannelServer) Start(spec string) (err error) {
        s.running = true
 
        // handle clients
+       sig := concurrent.NewSignaller()
        go func() {
                for s.running {
-                       select {
-                       case in := <-s.hdlr:
-                               if in == nil {
-                                       break
-                               }
-                               switch x := in.(type) {
-                               case Channel:
-                                       go s.handle(x)
-                               }
+                       in := <-s.hdlr
+                       if in == nil {
+                               break
+                       }
+                       switch x := in.(type) {
+                       case Channel:
+                               go s.handle(x, sig)
                        }
                }
                s.srvc.Close()
@@ -99,33 +103,41 @@ func (s *TestChannelServer) Stop() {
 func TestChannelServerTCPSingle(t *testing.T) {
        time.Sleep(time.Second)
        s := NewTestChannelServer()
-       if err := s.Start("tcp+" + TCP_ADDR_SERVER); err != nil {
+       err := s.Start("tcp+" + TCPAddrServer)
+       defer s.Stop()
+       if err != nil {
                t.Fatal(err)
        }
-       s.Stop()
 }
 
 func TestChannelServerTCPTwice(t *testing.T) {
        time.Sleep(time.Second)
        s1 := NewTestChannelServer()
-       if err := s1.Start("tcp+" + TCP_ADDR_SERVER); err != nil {
+       err := s1.Start("tcp+" + TCPAddrServer)
+       defer s1.Stop()
+       if err != nil {
                t.Fatal(err)
        }
+       time.Sleep(time.Second)
        s2 := NewTestChannelServer()
-       if err := s2.Start("tcp+" + TCP_ADDR_SERVER); err == nil {
+       err = s2.Start("tcp+" + TCPAddrServer)
+       defer s2.Stop()
+       if err == nil {
                t.Fatal("SocketServer started twice!!")
        }
-       s1.Stop()
 }
 
 func TestChannelClientTCP(t *testing.T) {
        time.Sleep(time.Second)
-       ch, err := NewChannel("tcp+" + TCP_ADDR_CLIENT)
+       ch, err := NewChannel("tcp+" + TCPAddrClient)
        if err != nil {
                t.Fatal(err)
        }
+       defer ch.Close()
+
+       sig := concurrent.NewSignaller()
        msg := []byte("GET /\n\n")
-       n, err := ch.Write(msg)
+       n, err := ch.Write(msg, sig)
        if err != nil {
                t.Fatal(err)
        }
@@ -136,29 +148,29 @@ func TestChannelClientTCP(t *testing.T) {
        n = 0
        start := time.Now().Unix()
        for n == 0 && (time.Now().Unix()-start) < 3 {
-               if n, err = ch.Read(buf); err != nil {
+               if n, err = ch.Read(buf, sig); err != nil {
                        t.Fatal(err)
                }
        }
-       if err = ch.Close(); err != nil {
-               t.Fatal(err)
-       }
        t.Logf("'%s' [%d]\n", string(buf[:n]), n)
 }
 
 func TestChannelClientServerTCP(t *testing.T) {
        time.Sleep(time.Second)
        s := NewTestChannelServer()
-       if err := s.Start("tcp+" + TCP_ADDR_SERVER); err != nil {
+       err := s.Start("tcp+" + TCPAddrServer)
+       defer s.Stop()
+       if err != nil {
                t.Fatal(err)
        }
 
-       ch, err := NewChannel("tcp+" + TCP_ADDR_SERVER)
+       ch, err := NewChannel("tcp+" + TCPAddrServer)
        if err != nil {
                t.Fatal(err)
        }
+       sig := concurrent.NewSignaller()
        msg := []byte("GET /\n\n")
-       n, err := ch.Write(msg)
+       n, err := ch.Write(msg, sig)
        if err != nil {
                t.Fatal(err)
        }
@@ -169,33 +181,32 @@ func TestChannelClientServerTCP(t *testing.T) {
        n = 0
        start := time.Now().Unix()
        for n == 0 && (time.Now().Unix()-start) < 3 {
-               if n, err = ch.Read(buf); err != nil {
+               if n, err = ch.Read(buf, sig); err != nil {
                        t.Fatal(err)
                }
        }
        if err = ch.Close(); err != nil {
                t.Fatal(err)
        }
-       if bytes.Compare(buf[:n], msg) != 0 {
+       if !bytes.Equal(buf[:n], msg) {
                t.Fatal("message send/receive mismatch")
        }
-
-       s.Stop()
 }
 
 func TestChannelClientServerSock(t *testing.T) {
        time.Sleep(time.Second)
        s := NewTestChannelServer()
-       if err := s.Start("unix+" + SOCK_ADDR); err != nil {
+       if err := s.Start("unix+" + SockAddr); err != nil {
                t.Fatal(err)
        }
 
-       ch, err := NewChannel("unix+" + SOCK_ADDR)
+       ch, err := NewChannel("unix+" + SockAddr)
        if err != nil {
                t.Fatal(err)
        }
+       sig := concurrent.NewSignaller()
        msg := []byte("This is just a test -- please ignore...")
-       n, err := ch.Write(msg)
+       n, err := ch.Write(msg, sig)
        if err != nil {
                t.Fatal(err)
        }
@@ -206,14 +217,14 @@ func TestChannelClientServerSock(t *testing.T) {
        n = 0
        start := time.Now().Unix()
        for n == 0 && (time.Now().Unix()-start) < 3 {
-               if n, err = ch.Read(buf); err != nil {
+               if n, err = ch.Read(buf, sig); err != nil {
                        t.Fatal(err)
                }
        }
        if err = ch.Close(); err != nil {
                t.Fatal(err)
        }
-       if bytes.Compare(buf[:n], msg) != 0 {
+       if !bytes.Equal(buf[:n], msg) {
                t.Fatal("message send/receive mismatch")
        }
 
diff --git a/src/gnunet/transport/connection.go 
b/src/gnunet/transport/connection.go
index 03549fc..cc2c909 100644
--- a/src/gnunet/transport/connection.go
+++ b/src/gnunet/transport/connection.go
@@ -29,9 +29,7 @@ import (
 type Connection struct {
        from, to  *core.Peer
        ch        *MsgChannel
-       buf       []byte
        bandwidth uint32
-       init      bool
        state     int
        shared    []byte
 }
diff --git a/src/gnunet/transport/session.go b/src/gnunet/transport/session.go
index f6a320e..f5a0787 100644
--- a/src/gnunet/transport/session.go
+++ b/src/gnunet/transport/session.go
@@ -20,10 +20,10 @@ package transport
 
 // Session states
 const (
-       KX_STATE_DOWN         = iota // No handshake yet.
-       KX_STATE_KEY_SENT            // We've sent our session key.
-       KX_STATE_KEY_RECEIVED        // We've received the other peers session 
key.
-       KX_STATE_UP                  // Key exchange is done.
-       KX_STATE_REKEY_SENT          // We're rekeying (or had a timeout).
-       KX_PEER_DISCONNECT           // Last state of a KX (when it is being 
terminated).
+       KxStateDown        = iota // No handshake yet.
+       KxStateKeySent            // We've sent our session key.
+       KxStateKeyReceived        // We've received the other peers session key.
+       KxStateUp                 // Key exchange is done.
+       KxStateRekeySent          // We're rekeying (or had a timeout).
+       KxPeerDisconnect          // Last state of a KX (when it is being 
terminated).
 )
diff --git a/src/gnunet/util/address.go b/src/gnunet/util/address.go
index 2d3e3ac..d272742 100644
--- a/src/gnunet/util/address.go
+++ b/src/gnunet/util/address.go
@@ -61,7 +61,7 @@ func AddressString(transport string, addr []byte) string {
 
 //----------------------------------------------------------------------
 
-// IP address (can be IPv4 or IPv6 or a DNS name)
+// IPAddress (can be IPv4 or IPv6 or a DNS name)
 type IPAddress struct {
        Host []byte `size:"*-2"`
        Port uint16 `order:"big"`
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index be25ef9..254610b 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -87,7 +87,7 @@ func Fill(b []byte, val byte) {
 // String list helpers
 //----------------------------------------------------------------------
 
-// Reverse StringList reverse an array of strings
+// ReverseStringList reverse an array of strings
 func ReverseStringList(s []string) []string {
        sl := len(s)
        r := make([]string, sl)
@@ -97,9 +97,9 @@ func ReverseStringList(s []string) []string {
        return r
 }
 
-// Convert a binary representation of a string list. Each string is '\0'-
-// terminated. The whole byte array is parsed; if the final string is not
-// terminated, it is skipped.
+// StringList converts a binary representation of a string list. Each string
+// is '\0'-terminated. The whole byte array is parsed; if the final string is
+// not terminated, it is skipped.
 func StringList(b []byte) []string {
        res := make([]string, 0)
        str := ""
diff --git a/src/gnunet/util/database.go b/src/gnunet/util/database.go
index 48da749..5805a8f 100644
--- a/src/gnunet/util/database.go
+++ b/src/gnunet/util/database.go
@@ -24,17 +24,17 @@ import (
        "os"
        "strings"
 
-       _ "github.com/go-sql-driver/mysql"
-       _ "github.com/mattn/go-sqlite3"
+       _ "github.com/go-sql-driver/mysql" // init MySQL driver
+       _ "github.com/mattn/go-sqlite3"    // init SQLite3 driver
 )
 
 // 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")
 )
 
-// ConnectSqlDatabase connects to an SQL database (various types and flavors):
+// ConnectSQLDatabase connects to an 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)
@@ -46,21 +46,21 @@ var (
 // * '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 ConnectSqlDatabase(spec string) (db *sql.DB, err error) {
+func ConnectSQLDatabase(spec string) (db *sql.DB, err error) {
        // split spec string into segments
        specs := strings.Split(spec, ":")
        if len(specs) < 2 {
-               return nil, ErrSqlInvalidDatabaseSpec
+               return nil, ErrSQLInvalidDatabaseSpec
        }
        switch specs[0] {
        case "sqlite3":
                // check if the database file exists
                var fi os.FileInfo
                if fi, err = os.Stat(specs[1]); err != nil {
-                       return nil, ErrSqlNoDatabase
+                       return nil, ErrSQLNoDatabase
                }
                if fi.IsDir() {
-                       return nil, ErrSqlNoDatabase
+                       return nil, ErrSQLNoDatabase
                }
                // open the database file
                return sql.Open("sqlite3", specs[1])
@@ -68,5 +68,5 @@ func ConnectSqlDatabase(spec string) (db *sql.DB, err error) {
                // just connect to the database
                return sql.Open("mysql", specs[1])
        }
-       return nil, ErrSqlInvalidDatabaseSpec
+       return nil, ErrSQLInvalidDatabaseSpec
 }
diff --git a/src/gnunet/util/id.go b/src/gnunet/util/id.go
index 14333bf..f2fb4f0 100644
--- a/src/gnunet/util/id.go
+++ b/src/gnunet/util/id.go
@@ -22,7 +22,8 @@ var (
        _id = 0
 )
 
-// generate next unique identifier (unique in the running process/application)
+// NextID generates the next unique identifier (unique in the running
+// process/application)
 func NextID() int {
        _id++
        return _id
diff --git a/src/gnunet/util/key_value_store.go 
b/src/gnunet/util/key_value_store.go
index d87b747..0658218 100644
--- a/src/gnunet/util/key_value_store.go
+++ b/src/gnunet/util/key_value_store.go
@@ -25,7 +25,7 @@ import (
        "strconv"
        "strings"
 
-       "github.com/go-redis/redis"
+       redis "github.com/go-redis/redis/v8"
 )
 
 // Error messages related to the key/value-store implementations
@@ -88,11 +88,11 @@ func OpenKVStore(spec string) (KeyValueStore, error) {
                //--------------------------------------------------------------
                // SQL-based persistance
                //--------------------------------------------------------------
-               kvs := new(KvsSql)
+               kvs := new(KvsSQL)
                var err error
 
                // connect to SQL database
-               kvs.db, err = ConnectSqlDatabase(spec)
+               kvs.db, err = ConnectSQLDatabase(spec)
                if err != nil {
                        return nil, err
                }
@@ -111,7 +111,7 @@ func OpenKVStore(spec string) (KeyValueStore, error) {
 // NoSQL-based key-value-stores
 //======================================================================
 
-// Redis-based key/value store
+// KvsRedis represents a redis-based key/value store
 type KvsRedis struct {
        client *redis.Client // client connection
        db     int           // index to database
@@ -127,7 +127,7 @@ func (kvs *KvsRedis) Get(key string) (value string, err 
error) {
        return kvs.client.Get(context.TODO(), key).Result()
 }
 
-// Get a list of all keys in store
+// List all keys in store
 func (kvs *KvsRedis) List() (keys []string, err error) {
        var (
                crs  uint64
@@ -151,26 +151,26 @@ func (kvs *KvsRedis) List() (keys []string, err error) {
 // SQL-based key-value-store
 //======================================================================
 
-// SQL-based key/value store
-type KvsSql struct {
+// KvsSQL represents a SQL-based key/value store
+type KvsSQL struct {
        db *sql.DB
 }
 
 // Put a key/value pair into the store
-func (kvs *KvsSql) Put(key string, value string) error {
+func (kvs *KvsSQL) Put(key string, value string) error {
        _, err := kvs.db.Exec("insert into store(key,value) values(?,?)", key, 
value)
        return err
 }
 
 // Get a value for a given key from store
-func (kvs *KvsSql) Get(key string) (value string, err error) {
+func (kvs *KvsSQL) Get(key string) (value string, err error) {
        row := kvs.db.QueryRow("select value from store where key=?", key)
        err = row.Scan(&value)
        return
 }
 
-// Get a list of all keys in store
-func (kvs *KvsSql) List() (keys []string, err error) {
+// List all keys in store
+func (kvs *KvsSQL) List() (keys []string, err error) {
        var (
                rows *sql.Rows
                key  string
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index 80f5c84..66744bd 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -22,9 +22,10 @@ import (
        "strings"
 )
 
-// CounterMap
+// CounterMap is a metric with single key
 type CounterMap map[interface{}]int
 
+// Add one to themetric for a given key and return current value
 func (cm CounterMap) Add(i interface{}) int {
        count, ok := cm[i]
        if !ok {
@@ -36,6 +37,7 @@ func (cm CounterMap) Add(i interface{}) int {
        return count
 }
 
+// Num returns the metric for a given key
 func (cm CounterMap) Num(i interface{}) int {
        count, ok := cm[i]
        if !ok {
@@ -44,6 +46,8 @@ func (cm CounterMap) Num(i interface{}) int {
        return count
 }
 
+// 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]
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index bf2a1c2..e70635c 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -113,7 +113,7 @@ func (t AbsoluteTime) Compare(t2 AbsoluteTime) int {
 // Relative time
 //----------------------------------------------------------------------
 
-// Relative time is a timestamp defined relative to the current time.
+// RelativeTime is a timestamp defined relative to the current time.
 // It actually is more like a duration than a time...
 type RelativeTime struct {
        Val uint64 `order:"big"`
diff --git a/src/gnunet/util/id.go b/src/gnunet/util/time_test.go
similarity index 66%
copy from src/gnunet/util/id.go
copy to src/gnunet/util/time_test.go
index 14333bf..d753d89 100644
--- a/src/gnunet/util/id.go
+++ b/src/gnunet/util/time_test.go
@@ -1,5 +1,5 @@
 // This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+// 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
@@ -18,12 +18,27 @@
 
 package util
 
-var (
-       _id = 0
+import (
+       "testing"
+       "time"
 )
 
-// generate next unique identifier (unique in the running process/application)
-func NextID() int {
-       _id++
-       return _id
+func TestTimeCompare(t *testing.T) {
+       t1 := AbsoluteTimeNow()
+       t2 := t1.Add(time.Hour)
+       t3 := t1.Add(24 * time.Hour)
+       tNever := AbsoluteTimeNever()
+
+       if t1.Compare(t2) != -1 {
+               t.Fatal("(1)")
+       }
+       if t1.Compare(t3) != -1 {
+               t.Fatal("(2)")
+       }
+       if t2.Compare(t3) != -1 {
+               t.Fatal("(3)")
+       }
+       if tNever.Compare(t1) != 1 {
+               t.Fatal("(4)")
+       }
 }
diff --git a/test.sh b/test.sh
index 5761694..2bcd9b4 100755
--- a/test.sh
+++ b/test.sh
@@ -1,3 +1,4 @@
 #!/bin/bash
 
-GOPATH=$(pwd):${GOPATH} go test -gcflags "-N -l" ./...
+cd src/gnunet/
+go test $* -gcflags "-N -l" ./...

-- 
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]