gnunet-svn
[Top][All Lists]
Advanced

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

[gnunet-go] branch master updated: Identity service added to zone master


From: gnunet
Subject: [gnunet-go] branch master updated: Identity service added to zone master.
Date: Wed, 26 Oct 2022 18:01:38 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new bcc6ce0  Identity service added to zone master.
bcc6ce0 is described below

commit bcc6ce0be0d9c240dce80c42af5b56e8ee805aff
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Wed Oct 26 17:59:46 2022 +0200

    Identity service added to zone master.
---
 src/gnunet/cmd/zonemaster-go/main.go               |  14 +-
 src/gnunet/config/gnunet-config.json               |   2 +-
 src/gnunet/crypto/gns.go                           | 158 ++++---
 src/gnunet/crypto/gns_edkey.go                     |  15 +-
 src/gnunet/crypto/gns_edkey_test.go                |   4 +
 src/gnunet/crypto/gns_pkey.go                      |  21 +-
 src/gnunet/crypto/gns_test.go                      |  51 +--
 .../zonemaster/rpc.go => enums/results.go}         |  15 +-
 src/gnunet/go.mod                                  |   4 +-
 src/gnunet/go.sum                                  |   4 +-
 src/gnunet/message/factory.go                      |  32 ++
 src/gnunet/message/message.go                      |   3 +
 src/gnunet/message/msg_core.go                     |   5 +-
 src/gnunet/message/msg_dht.go                      |  15 +
 src/gnunet/message/msg_dht_p2p.go                  |  61 ++-
 src/gnunet/message/msg_gns.go                      |   8 +-
 src/gnunet/message/msg_hello.go                    |  41 +-
 src/gnunet/message/msg_identity.go                 | 473 +++++++++++++++++++++
 src/gnunet/message/msg_namecache.go                |  18 +-
 src/gnunet/message/msg_namestore.go                |  57 ++-
 src/gnunet/message/msg_revocation.go               |  16 +-
 src/gnunet/message/msg_transport.go                |  27 ++
 src/gnunet/service/connection.go                   |   3 +
 src/gnunet/service/dht/blocks/gns.go               |   7 +-
 src/gnunet/service/dht/blocks/gns_test.go          |  75 ++--
 src/gnunet/service/namecache/module.go             |   2 +-
 src/gnunet/service/revocation/pow_test.go          |   8 +-
 src/gnunet/service/store/store_zonemaster.go       | 201 ++++++++-
 src/gnunet/service/store/store_zonemaster.sql      |  30 +-
 src/gnunet/service/zonemaster/gui.go               |  46 +-
 src/gnunet/service/zonemaster/gui.htpl             |   4 +-
 src/gnunet/service/zonemaster/gui_css.htpl         |  27 +-
 src/gnunet/service/zonemaster/gui_new.htpl         |   6 +-
 src/gnunet/service/zonemaster/gui_rr.htpl          |  40 +-
 src/gnunet/service/zonemaster/records.go           |  25 +-
 src/gnunet/service/zonemaster/rpc.go               |   2 +-
 src/gnunet/service/zonemaster/service.go           | 186 ++++++--
 src/gnunet/service/zonemaster/service_identity.go  | 135 ++++++
 src/gnunet/service/zonemaster/service_namestore.go |  90 ++++
 src/gnunet/service/zonemaster/zonemaster.go        | 137 +++---
 src/gnunet/transport/reader_writer.go              |   5 +-
 41 files changed, 1702 insertions(+), 371 deletions(-)

diff --git a/src/gnunet/cmd/zonemaster-go/main.go 
b/src/gnunet/cmd/zonemaster-go/main.go
index 1e4b985..6380db0 100644
--- a/src/gnunet/cmd/zonemaster-go/main.go
+++ b/src/gnunet/cmd/zonemaster-go/main.go
@@ -73,13 +73,11 @@ func main() {
                config.Cfg.ZoneMaster.GUI = gui
        }
 
-       // start a new namestore service under zonemaster umbrella
+       // start services under zonemaster umbrella
        ctx, cancel := context.WithCancel(context.Background())
-       srv, ok := zonemaster.NewService(ctx, nil).(*zonemaster.Service)
-       if !ok {
-               logger.Println(logger.ERROR, "[zonemaster] Failed to create 
service")
-               return
-       }
+       srv := zonemaster.NewService(ctx, nil)
+       go srv.Run(ctx)
+
        // start UDS listener if service is specified
        if config.Cfg.ZoneMaster.Service != nil {
                sockHdlr := service.NewSocketHandler("zonemaster", srv)
@@ -89,10 +87,6 @@ func main() {
                }
        }
 
-       // start a new ZONEMASTER (background service with HTTPS backend)
-       zm := zonemaster.NewZoneMaster(config.Cfg, srv)
-       go zm.Run(ctx)
-
        // handle command-line arguments for RPC
        if len(rpcEndp) > 0 {
                parts := strings.Split(rpcEndp, ":")
diff --git a/src/gnunet/config/gnunet-config.json 
b/src/gnunet/config/gnunet-config.json
index 5e9ec99..cd19450 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -91,7 +91,7 @@
         },
         "gui": "127.0.0.1:8100",
         "service": {
-            "socket": "${RT_USER}/gnunet-service-namestore-go.sock",
+            "socket": "${RT_USER}/gnunet-service-zonemaster-go.sock",
             "params": {
                 "perm": "0770"
             }
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index 301acb1..a1c26f6 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -129,6 +129,10 @@ type ZoneKeyImpl interface {
 type ZonePrivateImpl interface {
        ZoneAbstractImpl
 
+       // Prepare a random byte array to be used as a random
+       // private key of given type.
+       Prepare(rnd []byte) []byte
+
        // 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, error)
@@ -211,46 +215,80 @@ func GetImplementation(ztype enums.GNSType) 
*ZoneImplementation {
 
 // ZonePrivate represents the possible types of private zone keys (PKEY, 
EDKEY,...)
 type ZonePrivate struct {
-       ZoneKey
+       Type    enums.GNSType `json:"type" order:"big"`
+       KeyData []byte        `json:"key" size:"(KeySize)"`
 
        impl ZonePrivateImpl // reference to implementation
 }
 
-// NewZonePrivate returns a new initialized ZonePrivate instance. If no data is
-// provided, a new random key is created
-func NewZonePrivate(ztype enums.GNSType, d []byte) (zp *ZonePrivate, err 
error) {
+// NewZonePrivate returns a new initialized ZonePrivate instance of given 
ztype.
+// If no data is provided, a new random key is created. If data is provided, it
+// must be in the correct format specified by a ZonePrivate implementation.
+func NewZonePrivate(ztype enums.GNSType, zdata []byte) (zp *ZonePrivate, err 
error) {
        // get factory for given zone type
        impl, ok := zoneImpl[ztype]
        if !ok {
                return nil, ErrNoImplementation
        }
+       prvImpl := impl.NewPrivate()
+
        // init data available?
-       if d == nil {
+       if zdata == nil {
                // no: create random seed
-               d = make([]byte, impl.PrivateSize)
-               if _, err = rand.Read(d); err != nil {
+               zdata = make([]byte, impl.PrivateSize)
+               if _, err = rand.Read(zdata); err != nil {
                        return
                }
+               zdata = prvImpl.Prepare(zdata)
        }
        // assemble private zone key
        zp = &ZonePrivate{
-               ZoneKey{
-                       ztype,
-                       nil,
-                       nil,
-               },
-               nil,
+               Type:    ztype,
+               KeyData: zdata,
+               impl:    prvImpl,
        }
-       zp.impl = impl.NewPrivate()
-       if err = zp.impl.Init(d); err != nil {
-               return
+       err = zp.impl.Init(zdata)
+       return
+}
+
+// Init is called to setup internal state after unmarshalling object
+func (zp *ZonePrivate) Init() (err error) {
+       // check for initialized key
+       if zp.impl == nil {
+               impl, ok := zoneImpl[zp.Type]
+               if !ok {
+                       return ErrNoImplementation
+               }
+               zp.impl = impl.NewPrivate()
+               err = zp.impl.Init(zp.KeyData)
        }
-       zp.ZoneKey.KeyData = zp.impl.Public().Bytes()
-       zp.ZoneKey.impl = impl.NewPublic()
-       err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
        return
 }
 
+// Null returns a "NULL" ZonePrivate for a given key
+func NullZonePrivate(ztype enums.GNSType) (*ZonePrivate, uint16) {
+       // get factory for given zone type
+       impl, ok := zoneImpl[ztype]
+       if !ok {
+               return nil, 0
+       }
+       kd := make([]byte, impl.PrivateSize)
+       zp := &ZonePrivate{
+               Type:    ztype, // need key type for length calculation
+               KeyData: kd,    // untyped key data (all 0)
+               impl:    nil,   // no implementation!
+       }
+       return zp, uint16(len(zp.KeyData) + 4)
+}
+
+// Bytes returns the binary representation
+func (zp *ZonePrivate) Bytes() []byte {
+       buf := new(bytes.Buffer)
+       _ = binary.Write(buf, binary.BigEndian, zp.Type)
+       _, _ = buf.Write(zp.KeyData)
+       return buf.Bytes()
+}
+
 // KeySize returns the number of bytes of a key representation.
 // This method is used during serialization (Unmarshal).
 func (zp *ZonePrivate) KeySize() uint {
@@ -263,7 +301,7 @@ func (zp *ZonePrivate) KeySize() uint {
 // Derive key (key blinding)
 func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h 
*math.Int, err error) {
        // calculate derived key
-       key := zp.Public().Bytes()
+       key := zp.Public().KeyData
        h = deriveH(key, label, context)
        var derived ZonePrivateImpl
        if derived, h, err = zp.impl.Derive(h); err != nil {
@@ -271,15 +309,10 @@ func (zp *ZonePrivate) Derive(label, context string) (dzp 
*ZonePrivate, h *math.
        }
        // assemble derived pivate key
        dzp = &ZonePrivate{
-               ZoneKey{
-                       zp.Type,
-                       nil,
-                       nil,
-               },
-               derived,
+               Type:    zp.Type,
+               KeyData: derived.Bytes(),
+               impl:    derived,
        }
-       dzp.ZoneKey.KeyData = derived.Public().Bytes()
-       err = dzp.Init()
        return
 }
 
@@ -290,7 +323,12 @@ func (zp *ZonePrivate) Sign(data []byte) (sig 
*ZoneSignature, err error) {
 
 // Public returns the associated public key
 func (zp *ZonePrivate) Public() *ZoneKey {
-       return &zp.ZoneKey
+       impl := zp.impl.Public()
+       return &ZoneKey{
+               Type:    zp.Type,
+               KeyData: impl.Bytes(),
+               impl:    impl,
+       }
 }
 
 // ID returns the human-readable zone private key.
@@ -310,7 +348,7 @@ type ZoneKey struct {
        impl ZoneKeyImpl // reference to implementation
 }
 
-// Init a zone key where only the attributes have been read/deserialized.
+// Init is called to setup internal state after unmarshalling object
 func (zk *ZoneKey) Init() (err error) {
        if zk.impl == nil {
                // initialize implementation
@@ -335,6 +373,14 @@ func NewZoneKey(d []byte) (zk *ZoneKey, err error) {
        return
 }
 
+// Bytes returns the binary representation (can be used with 'init()')
+func (zk *ZoneKey) Bytes() []byte {
+       buf := new(bytes.Buffer)
+       _ = binary.Write(buf, binary.BigEndian, zk.Type)
+       _, _ = buf.Write(zk.KeyData)
+       return buf.Bytes()
+}
+
 // KeySize returns the number of bytes of a key representation.
 // This method is used during serialization (Unmarshal).
 func (zk *ZoneKey) KeySize() uint {
@@ -346,7 +392,7 @@ func (zk *ZoneKey) KeySize() uint {
 
 // Derive key (key blinding)
 func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int, 
err error) {
-       key := zk.Bytes()
+       key := zk.KeyData
        h = deriveH(key, label, context)
        var derived ZoneKeyImpl
        if derived, h, err = zk.impl.Derive(h); err != nil {
@@ -388,12 +434,6 @@ func (zk *ZoneKey) ID() string {
        return zk.impl.ID()
 }
 
-// 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)
@@ -420,23 +460,26 @@ type ZoneSignature struct {
        impl ZoneSigImpl // reference to implementation
 }
 
+// Init is called to setup internal state after unmarshalling object
 func (zs *ZoneSignature) Init() (err error) {
-       // initialize implementations
-       impl, ok := zoneImpl[zs.Type]
-       if !ok {
-               err = ErrUnknownZoneType
-               return
-       }
-       // set signature implementation
-       sig := impl.NewSignature()
-       if err = sig.Init(zs.Signature); err != nil {
-               return
+       if zs.impl == nil {
+               // initialize implementations
+               impl, ok := zoneImpl[zs.Type]
+               if !ok {
+                       err = ErrUnknownZoneType
+                       return
+               }
+               // set signature implementation
+               sig := impl.NewSignature()
+               if err = sig.Init(zs.Signature); err != nil {
+                       return
+               }
+               zs.impl = sig
+               // set public key implementation
+               zk := impl.NewPublic()
+               err = zk.Init(zs.KeyData)
+               zs.ZoneKey.impl = zk
        }
-       zs.impl = sig
-       // set public key implementation
-       zk := impl.NewPublic()
-       err = zk.Init(zs.KeyData)
-       zs.ZoneKey.impl = zk
        return
 }
 
@@ -460,6 +503,11 @@ func (zs *ZoneSignature) SigSize() uint {
        return 0
 }
 
+// Bytes returns the binary representation (can be used with 'init()')
+func (zs *ZoneSignature) Bytes() []byte {
+       return zs.impl.Bytes()
+}
+
 // Key returns the associated zone key object
 func (zs *ZoneSignature) Key() *ZoneKey {
        return &zs.ZoneKey
@@ -486,10 +534,10 @@ func deriveH(key []byte, label, context string) *math.Int 
{
        return math.NewIntFromBytes(b)
 }
 
-// convert (type|data) to GNUnet identifier
-func asID(t enums.GNSType, data []byte) string {
+// convert (type|data) to bytes
+func asBytes(t enums.GNSType, data []byte) []byte {
        buf := new(bytes.Buffer)
        _ = binary.Write(buf, binary.BigEndian, t)
        _, _ = buf.Write(data)
-       return util.EncodeBinaryToString(buf.Bytes())
+       return buf.Bytes()
 }
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
index 08dbc55..0054ca5 100644
--- a/src/gnunet/crypto/gns_edkey.go
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -79,8 +79,8 @@ func (pk *EDKEYPublicImpl) Bytes() []byte {
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
 func (pk *EDKEYPublicImpl) Derive(h *math.Int) (dPk ZoneKeyImpl, hOut 
*math.Int, err error) {
-       // limit to allowed value range
-       hOut = h.Mod(ed25519.GetCurve().N)
+       // limit to allowed value range (see LSD0001 spec)
+       hOut = h.SetBit(255, 0)
        derived := pk.pub.Mult(hOut)
        dPk = &EDKEYPublicImpl{
                pk.ztype,
@@ -162,7 +162,7 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expire 
util.AbsoluteTime) (ske
 
 // ID returns the GNUnet identifier for a public zone key
 func (pk *EDKEYPublicImpl) ID() string {
-       return asID(enums.GNS_TYPE_EDKEY, pk.pub.Bytes())
+       return util.EncodeBinaryToString(asBytes(enums.GNS_TYPE_EDKEY, 
pk.pub.Bytes()))
 }
 
 //----------------------------------------------------------------------
@@ -187,6 +187,11 @@ func (pk *EDKEYPrivateImpl) Init(data []byte) error {
        return nil
 }
 
+// Prepare a random byte array to be used as a random private EDKEY
+func (pk *EDKEYPrivateImpl) Prepare(rnd []byte) []byte {
+       return rnd
+}
+
 // Bytes returns a binary representation of the instance suitable for
 // consumption in 'Init()'.
 func (pk *EDKEYPrivateImpl) Bytes() []byte {
@@ -201,7 +206,7 @@ func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
 // Derive a public key from this key based on a big integer
 // (key blinding). Returns the derived key and the blinding value.
 func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut 
*math.Int, err error) {
-       // limit to allowed value range
+       // limit to allowed value range (see LSD0001 spec)
        hOut = h.SetBit(255, 0)
        // derive private key
        derived := pk.prv.Mult(hOut)
@@ -243,7 +248,7 @@ func (pk *EDKEYPrivateImpl) Sign(data []byte) (sig 
*ZoneSignature, err error) {
 
 // ID returns the GNUnet identifier for a private zone key
 func (pk *EDKEYPrivateImpl) ID() string {
-       return asID(enums.GNS_TYPE_EDKEY, pk.seed)
+       return util.EncodeBinaryToString(asBytes(enums.GNS_TYPE_EDKEY, pk.seed))
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_edkey_test.go 
b/src/gnunet/crypto/gns_edkey_test.go
index aa9728f..b5ba700 100644
--- a/src/gnunet/crypto/gns_edkey_test.go
+++ b/src/gnunet/crypto/gns_edkey_test.go
@@ -20,6 +20,7 @@ package crypto
 
 import (
        "bytes"
+       "encoding/hex"
        "gnunet/enums"
        "testing"
 )
@@ -50,7 +51,10 @@ func TestDeriveEDKEY(t *testing.T) {
        if err != nil {
                t.Fatal(err)
        }
+       // check resuts
        if !bytes.Equal(dzp.Public().Bytes(), dzk.Bytes()) {
+               t.Logf("dzp.Public = %s", 
hex.EncodeToString(dzp.Public().Bytes()))
+               t.Logf("dzk = %s", hex.EncodeToString(dzk.Bytes()))
                t.Fatal("derive mismatch")
        }
 }
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
index 8184483..d2c08ba 100644
--- a/src/gnunet/crypto/gns_pkey.go
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -158,7 +158,7 @@ func (pk *PKEYPublicImpl) cipher(encrypt bool, data []byte, 
label string, expire
 
 // ID returns the GNUnet identifier for a public zone key
 func (pk *PKEYPublicImpl) ID() string {
-       return asID(enums.GNS_TYPE_PKEY, pk.pub.Bytes())
+       return util.EncodeBinaryToString(asBytes(enums.GNS_TYPE_PKEY, 
pk.pub.Bytes()))
 }
 
 //----------------------------------------------------------------------
@@ -173,15 +173,25 @@ type PKEYPrivateImpl struct {
 }
 
 // Init instance from binary data. The data represents a big integer
-// (in big-endian notation) for the private scalar d.
+// (in little-endian notation) for the private scalar d (clamped).
 func (pk *PKEYPrivateImpl) Init(data []byte) error {
-       d := math.NewIntFromBytes(data)
+       // generate key material
+       d := math.NewIntFromBytes(util.Reverse(data))
        pk.prv = ed25519.NewPrivateKeyFromD(d)
        pk.ztype = enums.GNS_TYPE_PKEY
        pk.pub = pk.prv.Public()
        return nil
 }
 
+// Prepare a random byte array to be used as a random private PKEY
+func (pk *PKEYPrivateImpl) Prepare(rnd []byte) []byte {
+       // clamp little-endian skalar
+       d := util.Clone(rnd)
+       d[31] = (d[31] & 0x3f) | 0x40
+       d[0] &= 0xf8
+       return d
+}
+
 // Bytes returns a binary representation of the instance suitable for
 // consumption in 'Init()'.
 func (pk *PKEYPrivateImpl) Bytes() []byte {
@@ -232,8 +242,11 @@ func (pk *PKEYPrivateImpl) Sign(data []byte) (sig 
*ZoneSignature, err error) {
 }
 
 // ID returns the GNUnet identifier for a private zone key
+// (little-endian big integer)
 func (pk *PKEYPrivateImpl) ID() string {
-       return asID(enums.GNS_TYPE_PKEY, pk.prv.D.Bytes())
+       return util.EncodeBinaryToString(asBytes(
+               enums.GNS_TYPE_PKEY,
+               util.Reverse(pk.prv.D.Bytes())))
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index 2cccb4d..f910dcf 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -202,12 +202,13 @@ func TestVerifyBlock(t *testing.T) {
 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,
+                       // private scalar (clamped little-endian)
+                       0xc8, 0x93, 0xb0, 0x5d, 0xac, 0xff, 0x85, 0x1b,
+                       0xf5, 0xcc, 0x94, 0x24, 0x95, 0x71, 0xaf, 0x0e,
+                       0xcd, 0x9c, 0x1d, 0xfd, 0x1d, 0x6a, 0x0e, 0xab,
+                       0xe0, 0x11, 0x64, 0xef, 0x1d, 0xf7, 0x50, 0x74,
                }
+
                PUB = []byte{
                        // zone type
                        0x00, 0x01, 0x00, 0x00,
@@ -223,29 +224,19 @@ func TestDeriveH(t *testing.T) {
                CONTEXT = "gns"
 
                H = []byte{
-                       0x06, 0x5b, 0xb7, 0x42, 0x12, 0xa1, 0xae, 0xc3,
-                       0x59, 0x68, 0xdd, 0xdb, 0xca, 0xa3, 0x48, 0xfc,
-                       0xb0, 0xcd, 0x89, 0xd4, 0xcf, 0x9a, 0xe0, 0xfe,
-                       0xd1, 0xf9, 0xab, 0x6b, 0xd4, 0x28, 0xf4, 0x95,
+                       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
+                       // zone type (PKEY)
                        0x00, 0x01, 0x00, 0x00,
                        // derived public key data
-                       0xb1, 0x0e, 0x88, 0xd5, 0x17, 0x02, 0xf3, 0x3d,
-                       0xc9, 0xcb, 0xa1, 0xe9, 0x16, 0x65, 0x9c, 0x44,
-                       0x47, 0x9c, 0xc8, 0xdb, 0x83, 0x32, 0xd1, 0xd1,
-                       0xc5, 0x03, 0xdb, 0x50, 0x0e, 0xbd, 0x2d, 0x67,
-               }
-               QUERY = []byte{
-                       0xa9, 0x47, 0x81, 0x8a, 0xaf, 0x45, 0x94, 0xda,
-                       0x89, 0x41, 0xfa, 0x29, 0x77, 0x53, 0x94, 0x9d,
-                       0xcb, 0xc5, 0xfb, 0x41, 0xea, 0x77, 0xc6, 0x25,
-                       0x11, 0x3a, 0x59, 0x09, 0x32, 0xfe, 0xeb, 0xb4,
-                       0x59, 0x98, 0x69, 0xe2, 0x83, 0xe9, 0xdb, 0xd9,
-                       0xc7, 0x24, 0xeb, 0xf2, 0xd5, 0x30, 0x3b, 0x73,
-                       0xd7, 0xda, 0x9a, 0x2c, 0xd1, 0xd7, 0x95, 0x70,
-                       0xc5, 0x9d, 0x71, 0xb8, 0x32, 0x68, 0xc9, 0xd1,
+                       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,
                }
        )
 
@@ -255,7 +246,7 @@ func TestDeriveH(t *testing.T) {
                t.Fatal(err)
        }
 
-       // derive and checkpublic key
+       // derive and check public key
        pub := prv.Public()
        if !bytes.Equal(pub.Bytes(), PUB) {
                t.Fatal("wrong public key")
@@ -290,16 +281,6 @@ func TestDeriveH(t *testing.T) {
                }
                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) {
diff --git a/src/gnunet/service/zonemaster/rpc.go b/src/gnunet/enums/results.go
similarity index 76%
copy from src/gnunet/service/zonemaster/rpc.go
copy to src/gnunet/enums/results.go
index 2060e56..ea93b0e 100644
--- a/src/gnunet/service/zonemaster/rpc.go
+++ b/src/gnunet/enums/results.go
@@ -16,9 +16,16 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
-package zonemaster
+//nolint:stylecheck // allow non-camel-case for constants
+package enums
 
-import "gnunet/service"
+// ResultCode type
+type ResultCode int32
 
-func (s *Service) InitRPC(rpc *service.JRPCServer) {
-}
+// ResultCode values
+const (
+       RC_SYSERR ResultCode = -1
+       RC_NO     ResultCode = 0
+       RC_YES    ResultCode = 1
+       RC_OK     ResultCode = 1
+)
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index 185eaad..6e0021b 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -3,7 +3,7 @@ module gnunet
 go 1.18
 
 require (
-       github.com/bfix/gospel v1.2.20
+       github.com/bfix/gospel v1.2.21
        github.com/go-redis/redis/v8 v8.11.5
        github.com/go-sql-driver/mysql v1.6.0
        github.com/gorilla/mux v1.8.0
@@ -24,4 +24,4 @@ require (
        golang.org/x/tools v0.1.11 // indirect
 )
 
-// replace github.com/bfix/gospel v1.2.20 => ../gospel
+// replace github.com/bfix/gospel v1.2.21 => ../gospel
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index a8e3d7e..26ce2b7 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,5 +1,5 @@
-github.com/bfix/gospel v1.2.20 h1:e/IxmTiC579jIQlIxpMzCX/MIKHNsBzJ1WdMKheCgBw=
-github.com/bfix/gospel v1.2.20/go.mod 
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/bfix/gospel v1.2.21 h1:rgllMlR+2AZt6+x0uaBF67a+pM7fJxHiO93amhKXZNU=
+github.com/bfix/gospel v1.2.21/go.mod 
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
 github.com/cespare/xxhash/v2 v2.1.2 
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f 
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 82af6e9..e3a2943 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -31,6 +31,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
        //------------------------------------------------------------------
        // Transport
        //------------------------------------------------------------------
+
        case enums.MSG_TRANSPORT_TCP_WELCOME:
                return NewTransportTCPWelcomeMsg(nil), nil
        case enums.MSG_HELLO:
@@ -53,12 +54,14 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, 
error) {
        //------------------------------------------------------------------
        // Core
        //------------------------------------------------------------------
+
        case enums.MSG_CORE_EPHEMERAL_KEY:
                return NewEphemeralKeyMsg(), nil
 
        //------------------------------------------------------------------
        // DHT
        //------------------------------------------------------------------
+
        case enums.MSG_DHT_CLIENT_PUT:
                return NewDHTClientPutMsg(nil, 0, nil), nil
        case enums.MSG_DHT_CLIENT_GET:
@@ -73,6 +76,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
        //------------------------------------------------------------------
        // DHT-P2P
        //------------------------------------------------------------------
+
        case enums.MSG_DHT_P2P_HELLO:
                return NewDHTP2PHelloMsg(), nil
        case enums.MSG_DHT_P2P_GET:
@@ -85,6 +89,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
        //------------------------------------------------------------------
        // GNS
        //------------------------------------------------------------------
+
        case enums.MSG_GNS_LOOKUP:
                return NewGNSLookupMsg(), nil
        case enums.MSG_GNS_LOOKUP_RESULT:
@@ -93,6 +98,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
        //------------------------------------------------------------------
        // Namecache
        //------------------------------------------------------------------
+
        case enums.MSG_NAMECACHE_LOOKUP_BLOCK:
                return NewNamecacheLookupMsg(nil), nil
        case enums.MSG_NAMECACHE_LOOKUP_BLOCK_RESPONSE:
@@ -105,6 +111,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, 
error) {
        //------------------------------------------------------------------
        // Revocation
        //------------------------------------------------------------------
+
        case enums.MSG_REVOCATION_QUERY:
                return NewRevocationQueryMsg(nil), nil
        case enums.MSG_REVOCATION_QUERY_RESPONSE:
@@ -114,9 +121,33 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, 
error) {
        case enums.MSG_REVOCATION_REVOKE_RESPONSE:
                return NewRevocationRevokeResponseMsg(false), nil
 
+       //------------------------------------------------------------------
+       // Identity service
+       //------------------------------------------------------------------
+
+       case enums.MSG_IDENTITY_START:
+               return NewIdentityStartMsg(), nil
+       case enums.MSG_IDENTITY_RESULT_CODE:
+               return NewIdentityResultCodeMsg(enums.RC_OK, ""), nil
+       case enums.MSG_IDENTITY_UPDATE:
+               return NewIdentityUpdateMsg("", nil), nil
+       case enums.MSG_IDENTITY_CREATE:
+               return NewIdentityCreateMsg(nil, ""), nil
+       case enums.MSG_IDENTITY_RENAME:
+               return NewIdentityRenameMsg("", ""), nil
+       case enums.MSG_IDENTITY_DELETE:
+               return NewIdentityDeleteMsg(""), nil
+       case enums.MSG_IDENTITY_LOOKUP:
+               return NewIdentityLookupMsg(""), nil
+       case enums.MSG_IDENTITY_GET_DEFAULT:
+               return NewIdentityGetDefaultMsg(""), nil
+       case enums.MSG_IDENTITY_SET_DEFAULT:
+               return NewIdentitySetDefaultMsg(nil, ""), nil
+
        //------------------------------------------------------------------
        // Namestore service
        //------------------------------------------------------------------
+
        case enums.MSG_NAMESTORE_ZONE_ITERATION_START:
                return NewNamestoreZoneIterStartMsg(nil), nil
        case enums.MSG_NAMESTORE_ZONE_ITERATION_NEXT:
@@ -130,6 +161,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, 
error) {
        case enums.MSG_NAMESTORE_MONITOR_START:
        case enums.MSG_NAMESTORE_MONITOR_SYNC:
        case enums.MSG_NAMESTORE_RECORD_RESULT:
+               return NewNamestoreRecordResultMsg(nil, ""), nil
        case enums.MSG_NAMESTORE_MONITOR_NEXT:
        }
        return nil, fmt.Errorf("unknown message type %d", msgType)
diff --git a/src/gnunet/message/message.go b/src/gnunet/message/message.go
index 946dd6e..7e9b2d1 100644
--- a/src/gnunet/message/message.go
+++ b/src/gnunet/message/message.go
@@ -43,6 +43,9 @@ type Message interface {
 
        // String returns a human-readable message
        String() string
+
+       // Init called after unmarshalling a message to setup internal state
+       Init() error
 }
 
 //----------------------------------------------------------------------
diff --git a/src/gnunet/message/msg_core.go b/src/gnunet/message/msg_core.go
index 6ebb30e..227b901 100644
--- a/src/gnunet/message/msg_core.go
+++ b/src/gnunet/message/msg_core.go
@@ -45,7 +45,7 @@ type EphKeyBlock struct {
 type EphemeralKeyMsg struct {
        MsgHeader
        SenderStatus uint32              `order:"big"` // enum PeerStateMachine
-       Signature    *util.PeerSignature ``            // EdDSA signature
+       Signature    *util.PeerSignature `init:"Init"` // EdDSA signature
        SignedBlock  *EphKeyBlock
 }
 
@@ -68,6 +68,9 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *EphemeralKeyMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *EphemeralKeyMsg) String() string {
        return 
fmt.Sprintf("EphKeyMsg{peer=%s,ephkey=%s,create=%s,expire=%s,status=%d}",
diff --git a/src/gnunet/message/msg_dht.go b/src/gnunet/message/msg_dht.go
index 0f8b9cc..2e99d40 100644
--- a/src/gnunet/message/msg_dht.go
+++ b/src/gnunet/message/msg_dht.go
@@ -67,6 +67,9 @@ func (m *DHTClientPutMsg) String() string {
                m.BType, m.Expire, m.Options, m.ReplLevel, m.Key)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTClientPutMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // DHT_CLIENT_GET
 //----------------------------------------------------------------------
@@ -113,6 +116,9 @@ func (m *DHTClientGetMsg) String() string {
                m.ID, m.BType, m.Options, m.ReplLevel, m.Key)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTClientGetMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // DHT_CLIENT_RESULT
 //----------------------------------------------------------------------
@@ -153,6 +159,9 @@ func (m *DHTClientResultMsg) String() string {
        return fmt.Sprintf("DHTClientResultMsg{id:%d,type=%s,expire=%s}", m.ID, 
m.BType, m.Expire)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTClientResultMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // DHT_CLIENT_GET_STOP
 //----------------------------------------------------------------------
@@ -183,6 +192,9 @@ func (m *DHTClientGetStopMsg) String() string {
        return fmt.Sprintf("DHTClientGetStopMsg{Id:%d,Key=%s}", m.ID, m.Key)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTClientGetStopMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // DHT_CLIENT_GET_RESULTS_KNOWN
 //----------------------------------------------------------------------
@@ -220,3 +232,6 @@ func (m *DHTClientGetResultsKnownMsg) String() string {
        return fmt.Sprintf("DHTClientGetResultsKnownMsg{Id:%d,Key=%s,Num=%d}",
                m.ID, m.Key.Data, len(m.Known))
 }
+
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTClientGetResultsKnownMsg) Init() error { return nil }
diff --git a/src/gnunet/message/msg_dht_p2p.go 
b/src/gnunet/message/msg_dht_p2p.go
index d4a474c..f7c24fd 100644
--- a/src/gnunet/message/msg_dht_p2p.go
+++ b/src/gnunet/message/msg_dht_p2p.go
@@ -76,6 +76,9 @@ func NewDHTP2PGetMsg() *DHTP2PGetMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTP2PGetMsg) Init() (err error) { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *DHTP2PGetMsg) String() string {
        return fmt.Sprintf("DHTP2PGetMsg{btype=%s,hops=%d,flags=%s}",
@@ -167,6 +170,11 @@ func (m *DHTP2PPutMsg) IsUsed(field string) bool {
        return false
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTP2PPutMsg) Init() (err error) {
+       return nil
+}
+
 //----------------------------------------------------------------------
 
 // Update message (forwarding)
@@ -287,17 +295,17 @@ func (m *DHTP2PPutMsg) String() string {
 // DHTP2PResultMsg wire layout
 type DHTP2PResultMsg struct {
        MsgHeader
-       BType       enums.BlockType     `order:"big"`      // Block type of 
result
-       Reserved    uint16              `order:"big"`      // Reserved
-       Flags       uint16              `order:"big"`      // Message flags
-       PutPathL    uint16              `order:"big"`      // size of PUTPATH 
field
-       GetPathL    uint16              `order:"big"`      // size of GETPATH 
field
-       Expire      util.AbsoluteTime   ``                 // expiration date
-       Query       *crypto.HashCode    ``                 // Query key for 
block
-       TruncOrigin *util.PeerID        `opt:"(IsUsed)"`   // truncated origin 
(if TRUNCATED flag set)
-       PathList    []*path.Entry       `size:"(NumPath)"` // PATH
-       LastSig     *util.PeerSignature `opt:"(IsUsed)"`   // signature of last 
hop (if RECORD_ROUTE flag is set)
-       Block       []byte              `size:"*"`         // block data
+       BType       enums.BlockType     `order:"big"`                // Block 
type of result
+       Reserved    uint16              `order:"big"`                // Reserved
+       Flags       uint16              `order:"big"`                // Message 
flags
+       PutPathL    uint16              `order:"big"`                // size of 
PUTPATH field
+       GetPathL    uint16              `order:"big"`                // size of 
GETPATH field
+       Expire      util.AbsoluteTime   ``                           // 
expiration date
+       Query       *crypto.HashCode    ``                           // Query 
key for block
+       TruncOrigin *util.PeerID        `opt:"(IsUsed)"`             // 
truncated origin (if TRUNCATED flag set)
+       PathList    []*path.Entry       `size:"(NumPath)"`           // PATH
+       LastSig     *util.PeerSignature `opt:"(IsUsed)" init:"Init"` // 
signature of last hop (if RECORD_ROUTE flag is set)
+       Block       []byte              `size:"*"`                   // block 
data
 }
 
 // NewDHTP2PResultMsg creates a new empty DHTP2PResultMsg
@@ -330,6 +338,11 @@ func (m *DHTP2PResultMsg) NumPath(field string) uint {
        return uint(m.GetPathL + m.PutPathL)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTP2PResultMsg) Init() (err error) {
+       return nil
+}
+
 //----------------------------------------------------------------------
 // Path handling (get/set path in message)
 //----------------------------------------------------------------------
@@ -471,9 +484,12 @@ type DHTP2PHelloMsg struct {
        MsgHeader
        Reserved  uint16              `order:"big"` // Reserved for further use
        NumAddr   uint16              `order:"big"` // Number of addresses in 
list
-       Signature *util.PeerSignature ``            // Signature
+       Signature *util.PeerSignature `init:"Init"` // Signature
        Expire    util.AbsoluteTime   ``            // expiration time
        AddrList  []byte              `size:"*"`    // List of end-point 
addresses (HelloAddress)
+
+       // transient state
+       addresses []*util.Address // list of converted addresses
 }
 
 // NewHelloMsgDHT creates an empty DHT_P2P_HELLO message.
@@ -492,8 +508,12 @@ func NewDHTP2PHelloMsg() *DHTP2PHelloMsg {
        }
 }
 
-// Addresses returns the list of HelloAddress
-func (m *DHTP2PHelloMsg) Addresses() (list []*util.Address, err error) {
+// Init called after unmarshalling a message to setup internal state
+func (m *DHTP2PHelloMsg) Init() (err error) {
+       if m.addresses != nil {
+               return
+       }
+       m.addresses = make([]*util.Address, 0)
        var addr *util.Address
        var as string
        num, pos := 0, 0
@@ -506,16 +526,25 @@ func (m *DHTP2PHelloMsg) Addresses() (list 
[]*util.Address, err error) {
                        return
                }
                addr.Expire = m.Expire
-               list = append(list, addr)
+               m.addresses = append(m.addresses, addr)
                num++
        }
        // check numbers
        if num != int(m.NumAddr) {
-               logger.Printf(logger.WARN, "[DHTP2PHelloMsg] Number of 
addresses does not match (got %d, expected %d)", num, m.NumAddr)
+               err = errors.New("number of addresses does not match")
        }
        return
 }
 
+// Addresses returns the list of HelloAddress
+func (m *DHTP2PHelloMsg) Addresses() (list []*util.Address, err error) {
+       if m.addresses == nil {
+               err = errors.New("no addresses available")
+               return
+       }
+       return m.addresses, nil
+}
+
 // SetAddresses adds addresses to the HELLO message.
 func (m *DHTP2PHelloMsg) SetAddresses(list []*util.Address) {
        // write addresses as blob and track earliest expiration
diff --git a/src/gnunet/message/msg_gns.go b/src/gnunet/message/msg_gns.go
index 1c7f9af..64f58a4 100644
--- a/src/gnunet/message/msg_gns.go
+++ b/src/gnunet/message/msg_gns.go
@@ -37,7 +37,7 @@ import (
 type LookupMsg struct {
        MsgHeader
        ID       uint32          `order:"big"` // Unique identifier for this 
request (for key collisions).
-       Zone     *crypto.ZoneKey ``            // Zone that is to be used for 
lookup
+       Zone     *crypto.ZoneKey `init:"Init"` // 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
        RType    enums.GNSType   `order:"big"` // the type of record to look up
@@ -81,6 +81,9 @@ func (m *LookupMsg) String() string {
                m.ID, m.Zone.ID(), m.Options, m.RType, m.GetName())
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *LookupMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // GNS_LOOKUP_RESULT
 //----------------------------------------------------------------------
@@ -124,3 +127,6 @@ func (m *LookupResultMsg) String() string {
 func (m *LookupResultMsg) Header() *MsgHeader {
        return &MsgHeader{m.MsgSize, m.MsgType}
 }
+
+// Init called after unmarshalling a message to setup internal state
+func (m *LookupResultMsg) Init() error { return nil }
diff --git a/src/gnunet/message/msg_hello.go b/src/gnunet/message/msg_hello.go
index 4cc8712..73f2d9e 100644
--- a/src/gnunet/message/msg_hello.go
+++ b/src/gnunet/message/msg_hello.go
@@ -145,6 +145,32 @@ type HelloMsg struct {
        FriendsOnly uint32       `order:"big"` // Do not gossip this HELLO 
message
        Peer        *util.PeerID ``            // peer identifier for addresses
        AddrList    []byte       `size:"*"`    // List of end-point addresses 
(HelloAddress)
+
+       // transient state
+       addresses []*HelloAddress // list of converted addresses
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (m *HelloMsg) Init() (err error) {
+       // check for initialized state
+       if m.addresses != nil {
+               return nil
+       }
+       // convert addresses
+       m.addresses = make([]*HelloAddress, 0)
+       rdr := bytes.NewReader(m.AddrList)
+       var addr *HelloAddress
+       for {
+               // parse address from stream
+               if addr, err = ParseHelloAddr(rdr); err != nil {
+                       // end of stream: no more addresses
+                       if err == io.EOF {
+                               err = nil
+                       }
+                       return
+               }
+               m.addresses = append(m.addresses, addr)
+       }
 }
 
 // NewHelloMsg creates a new HELLO msg for a given peer.
@@ -164,19 +190,10 @@ func NewHelloMsg(peer *util.PeerID) *HelloMsg {
 
 // Addresses returns the list of HelloAddress
 func (m *HelloMsg) Addresses() (list []*HelloAddress, err error) {
-       rdr := bytes.NewReader(m.AddrList)
-       var addr *HelloAddress
-       for {
-               // parse address from stream
-               if addr, err = ParseHelloAddr(rdr); err != nil {
-                       // end of stream: no more addresses
-                       if err == io.EOF {
-                               err = nil
-                       }
-                       return
-               }
-               list = append(list, addr)
+       if m.addresses == nil {
+               return
        }
+       return m.addresses, nil
 }
 
 // String returns a human-readable representation of the message.
diff --git a/src/gnunet/message/msg_identity.go 
b/src/gnunet/message/msg_identity.go
new file mode 100644
index 0000000..fb8bba5
--- /dev/null
+++ b/src/gnunet/message/msg_identity.go
@@ -0,0 +1,473 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package message
+
+import (
+       "fmt"
+       "gnunet/crypto"
+       "gnunet/enums"
+       "gnunet/util"
+)
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_START
+//
+// Start client connection for update notification. Triggers sending
+// all identities as update messages to client.
+//----------------------------------------------------------------------
+
+// IdentityStartMsg to initiate session.
+type IdentityStartMsg struct {
+       MsgHeader
+}
+
+// NewIdentityStartMsg creates an empty message
+func NewIdentityStartMsg() *IdentityStartMsg {
+       return &IdentityStartMsg{
+               MsgHeader: MsgHeader{4, enums.MSG_IDENTITY_START},
+       }
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityStartMsg) Init() error { return nil }
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityStartMsg) String() string {
+       return "IdentityStartMsg{}"
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_UPDATE
+//
+// IdentityStore changed (send to all clients with started session)
+//----------------------------------------------------------------------
+
+// IdentityUpdateMsg notifies about changes in identity store
+type IdentityUpdateMsg struct {
+       MsgHeader
+
+       NameLen uint16              `order:"big"`
+       EOL     uint16              `order:"big"`
+       ZoneKey *crypto.ZonePrivate `init:"Init"`
+       Name_   []byte              `size:"NameLen"`
+
+       // transient state
+       name string
+}
+
+// NewIdentityUpdateMsg creates an update message. If the zone key is nil,
+// a End-Of-List is triggered so the client knows we are done.
+func NewIdentityUpdateMsg(name string, zk *crypto.ZonePrivate) 
*IdentityUpdateMsg {
+       msg := &IdentityUpdateMsg{
+               MsgHeader: MsgHeader{8, enums.MSG_IDENTITY_UPDATE},
+       }
+       if zk == nil {
+               // tag end-of-list
+               msg.EOL = uint16(enums.RC_YES)
+               var size uint16
+               // assemble an empty zonekey
+               msg.ZoneKey, size = crypto.NullZonePrivate(enums.GNS_TYPE_PKEY)
+               msg.MsgSize += size
+       } else {
+               msg.name = name
+               msg.Name_ = util.WriteCString(name)
+               msg.NameLen = uint16(len(msg.Name_))
+               msg.MsgSize += msg.NameLen
+               msg.ZoneKey = zk
+               msg.MsgSize += uint16(zk.KeySize() + 4)
+       }
+       return msg
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityUpdateMsg) Init() error {
+       msg.name, _ = util.ReadCString(msg.Name_, 0)
+       return nil
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityUpdateMsg) String() string {
+       if msg.EOL == uint16(enums.RC_OK) {
+               return "IdentityUpdateMsg{end-of-list}"
+       }
+       return fmt.Sprintf("IdentityUpdateMsg{'%s'@%s}", msg.Name(), 
msg.ZoneKey.ID())
+}
+
+// Name of the new identity
+func (msg *IdentityUpdateMsg) Name() string {
+       return msg.name
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_RESULT_CODE
+//
+// Returned by CREATE and RENAME (and by GET_DEFAULT on failure).
+//----------------------------------------------------------------------
+
+// IdentityResultCodeMsg is a response message
+type IdentityResultCodeMsg struct {
+       MsgHeader
+
+       ResultCode enums.ResultCode `order:"big"`
+       Error      string           `opt:"(OnError)"`
+}
+
+// OnError returns true if an error message is attached
+func (msg *IdentityResultCodeMsg) OnError() bool {
+       return msg.ResultCode != enums.RC_OK
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityResultCodeMsg) Init() error { return nil }
+
+// NewIdentityResultCodeMsg creates a new default message.
+func NewIdentityResultCodeMsg(rc enums.ResultCode, err string) 
*IdentityResultCodeMsg {
+       msg := &IdentityResultCodeMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: 8,
+                       MsgType: enums.MSG_IDENTITY_RESULT_CODE,
+               },
+               ResultCode: rc,
+       }
+       if rc != enums.RC_OK {
+               msg.Error = err
+               msg.MsgSize += uint16(len(err) + 1)
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityResultCodeMsg) String() string {
+       return fmt.Sprintf("IdentityResultCodeMsg{rc=%d,err='%s'}", 
msg.ResultCode, msg.Error)
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_CREATE
+//
+// Create new identity with service association
+//----------------------------------------------------------------------
+
+// IdentityCreateMsg to create a new identity for given service
+type IdentityCreateMsg struct {
+       MsgHeader
+
+       NameLen  uint16              `order:"big"`
+       Reserved uint16              `order:"big"`
+       ZoneKey  *crypto.ZonePrivate `init:"Init"`
+       Name_    []byte              `size:"NameLen"`
+
+       // transient state
+       name string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityCreateMsg) Init() error {
+       msg.name, _ = util.ReadCString(msg.Name_, 0)
+       return nil
+}
+
+// NewIdentityCreateMsg creates a new default message.
+func NewIdentityCreateMsg(zk *crypto.ZonePrivate, name string) 
*IdentityCreateMsg {
+       var size uint16
+       if zk == nil {
+               zk, size = crypto.NullZonePrivate(enums.GNS_TYPE_PKEY)
+       } else {
+               size = uint16(zk.KeySize() + 4)
+       }
+       msg := &IdentityCreateMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: size + 8,
+                       MsgType: enums.MSG_IDENTITY_CREATE,
+               },
+               ZoneKey: zk,
+       }
+       if len(name) > 0 {
+               msg.Name_ = util.WriteCString(name)
+               msg.MsgSize += uint16(len(msg.Name_))
+               msg.name = name
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityCreateMsg) String() string {
+       return fmt.Sprintf("IdentityCreateMsg{name='%s',key=%s}", msg.name, 
msg.ZoneKey.ID())
+}
+
+// Name of the new identity
+func (msg *IdentityCreateMsg) Name() string {
+       return msg.name
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_RENAME
+//
+// Rename identity
+//----------------------------------------------------------------------
+
+// IdentitRenameMsg to rename an identity
+type IdentityRenameMsg struct {
+       MsgHeader
+
+       OldNameLen uint16 `order:"big"`
+       NewNameLen uint16 `order:"big"`
+       OldName_   []byte `size:"OldNameLen"`
+       NewName_   []byte `size:"NewNameLen"`
+
+       // transient state
+       oldName string
+       newName string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityRenameMsg) Init() error {
+       msg.oldName, _ = util.ReadCString(msg.OldName_, 0)
+       msg.newName, _ = util.ReadCString(msg.NewName_, 0)
+       return nil
+}
+
+// NewIdentityRenameMsg renames an identity
+func NewIdentityRenameMsg(oldName, newName string) *IdentityRenameMsg {
+       msg := &IdentityRenameMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: 8,
+                       MsgType: enums.MSG_IDENTITY_RENAME,
+               },
+       }
+       if len(oldName) > 0 {
+               msg.OldName_ = util.WriteCString(oldName)
+               msg.MsgSize += uint16(len(msg.OldName_))
+               msg.oldName = oldName
+       }
+       if len(newName) > 0 {
+               msg.NewName_ = util.WriteCString(newName)
+               msg.MsgSize += uint16(len(msg.NewName_))
+               msg.newName = newName
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityRenameMsg) String() string {
+       return fmt.Sprintf("IdentityRenameMsg{'%s'->'%s'}", msg.oldName, 
msg.newName)
+}
+
+// OldName of the identity
+func (msg *IdentityRenameMsg) OldName() string {
+       return msg.oldName
+}
+
+// NewName of the identity
+func (msg *IdentityRenameMsg) NewName() string {
+       return msg.newName
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_DELETE
+//
+// Remove named identity
+//----------------------------------------------------------------------
+
+// IdentityDeleteMsg requests the deletion of an identity
+type IdentityDeleteMsg struct {
+       MsgHeader
+
+       NameLen  uint16 `order:"big"`
+       Reserved uint16 `order:"big"`
+       Name_    []byte `size:"NameLen"`
+
+       // transient state
+       name string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityDeleteMsg) Init() error {
+       msg.name, _ = util.ReadCString(msg.Name_, 0)
+       return nil
+}
+
+// NewIdentityDeleteMsg renames an identity
+func NewIdentityDeleteMsg(name string) *IdentityDeleteMsg {
+       msg := &IdentityDeleteMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: 8,
+                       MsgType: enums.MSG_IDENTITY_DELETE,
+               },
+       }
+       if len(name) > 0 {
+               msg.Name_ = util.WriteCString(name)
+               msg.MsgSize += uint16(len(msg.Name_))
+               msg.name = name
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityDeleteMsg) String() string {
+       return fmt.Sprintf("IdentityDeleteMsg{name='%s'}", msg.name)
+}
+
+// Name of the removed identity
+func (msg *IdentityDeleteMsg) Name() string {
+       return msg.name
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_LOOKUP
+//
+// Return default identity
+//----------------------------------------------------------------------
+
+// IdentityLookupMsg to lookup named identity
+type IdentityLookupMsg struct {
+       MsgHeader
+
+       Name string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityLookupMsg) Init() error {
+       return nil
+}
+
+// NewIdentityLookupMsg renames an identity
+func NewIdentityLookupMsg(name string) *IdentityLookupMsg {
+       return &IdentityLookupMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: uint16(len(name) + 9),
+                       MsgType: enums.MSG_IDENTITY_DELETE,
+               },
+               Name: name,
+       }
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityLookupMsg) String() string {
+       return fmt.Sprintf("IdentityLookupMsg{name='%s'}", msg.Name)
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_GET_DEFAULT
+//
+// Get the default identity for named subsystem
+//----------------------------------------------------------------------
+
+// IdentityGetDefault to retrieve the default identity for a service
+type IdentityGetDefaultMsg struct {
+       MsgHeader
+
+       SrvLen   uint16 `order:"big"`
+       Reserved uint16 `order:"big"`
+       Service_ []byte `size:"SrvLen"`
+
+       // transient state
+       service string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentityGetDefaultMsg) Init() error {
+       msg.service, _ = util.ReadCString(msg.Service_, 0)
+       return nil
+}
+
+// NewIdentityGetDefaultMsg creates a new message
+func NewIdentityGetDefaultMsg(svc string) *IdentityGetDefaultMsg {
+       msg := &IdentityGetDefaultMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: 8,
+                       MsgType: enums.MSG_IDENTITY_DELETE,
+               },
+       }
+       if len(svc) > 0 {
+               msg.Service_ = util.WriteCString(svc)
+               msg.MsgSize += uint16(len(msg.Service_))
+               msg.service = svc
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentityGetDefaultMsg) String() string {
+       return fmt.Sprintf("IdentityGetDefaultMsg{svc='%s'}", msg.service)
+}
+
+// Service name
+func (msg *IdentityGetDefaultMsg) Service() string {
+       return msg.service
+}
+
+//----------------------------------------------------------------------
+// MSG_IDENTITY_SET_DEFAULT
+//
+// Set default identity for named subsystem
+//----------------------------------------------------------------------
+
+// IdentitySetDefaultMsg sets a default identity (key) for a service
+type IdentitySetDefaultMsg struct {
+       MsgHeader
+
+       SrvLen   uint16              `order:"big"`
+       Reserved uint16              `order:"big"`
+       ZoneKey  *crypto.ZonePrivate `init:"Init"`
+       Service_ []byte              `size:"SrvLen"`
+
+       // transient state
+       service string
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (msg *IdentitySetDefaultMsg) Init() error {
+       msg.service, _ = util.ReadCString(msg.Service_, 0)
+       return nil
+}
+
+// NewIdentitySetDefaultMsg renames an identity
+func NewIdentitySetDefaultMsg(zk *crypto.ZonePrivate, svc string) 
*IdentitySetDefaultMsg {
+       msg := &IdentitySetDefaultMsg{
+               MsgHeader: MsgHeader{
+                       MsgSize: 8,
+                       MsgType: enums.MSG_IDENTITY_DELETE,
+               },
+       }
+       if zk == nil {
+               // assemble an empty zonekey
+               var size uint16
+               msg.ZoneKey, size = crypto.NullZonePrivate(enums.GNS_TYPE_PKEY)
+               msg.MsgSize += size
+       } else {
+               msg.ZoneKey = zk
+               msg.MsgSize += uint16(zk.KeySize() + 4)
+       }
+       if len(svc) > 0 {
+               msg.Service_ = util.WriteCString(svc)
+               msg.MsgSize += uint16(len(msg.Service_))
+               msg.service = svc
+       }
+       return msg
+}
+
+// String returns a human-readable representation of the message.
+func (msg *IdentitySetDefaultMsg) String() string {
+       return fmt.Sprintf("IdentitySetDefaultMsg{key=%s,svc='%s'}", 
msg.ZoneKey.ID(), msg.service)
+}
+
+// Service name
+func (msg *IdentitySetDefaultMsg) Service() string {
+       return msg.service
+}
diff --git a/src/gnunet/message/msg_namecache.go 
b/src/gnunet/message/msg_namecache.go
index 53b2f4c..61309aa 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -67,6 +67,9 @@ func NewNamecacheLookupMsg(query *crypto.HashCode) 
*NamecacheLookupMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamecacheLookupMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *NamecacheLookupMsg) String() string {
        return fmt.Sprintf("NamecacheLookupMsg{Id=%d,Query=%s}",
@@ -81,11 +84,14 @@ func (m *NamecacheLookupMsg) String() string {
 type NamecacheLookupResultMsg struct {
        GenericNamecacheMsg
 
-       Expire        util.AbsoluteTime     ``         // Expiration time
-       DerivedKeySig *crypto.ZoneSignature ``         // Derived public key
-       EncData       []byte                `size:"*"` // Encrypted block data
+       Expire        util.AbsoluteTime     ``            // Expiration time
+       DerivedKeySig *crypto.ZoneSignature `init:"Init"` // Derived public key
+       EncData       []byte                `size:"*"`    // Encrypted block 
data
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamecacheLookupResultMsg) Init() error { return nil }
+
 // NewNamecacheLookupResultMsg creates a new default message.
 func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg {
        return &NamecacheLookupResultMsg{
@@ -116,6 +122,9 @@ type NamecacheCacheMsg struct {
        EncData    []byte            `size:"*"`         // Encrypted block data
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamecacheCacheMsg) Init() error { return nil }
+
 // Size returns buffer sizes for fields
 func (m *NamecacheCacheMsg) FldSize(field string) uint {
        switch field {
@@ -174,6 +183,9 @@ func NewNamecacheCacheResponseMsg() 
*NamecacheCacheResponseMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamecacheCacheResponseMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *NamecacheCacheResponseMsg) String() string {
        return fmt.Sprintf("NamecacheCacheResponseMsg{id=%d,result=%d}",
diff --git a/src/gnunet/message/msg_namestore.go 
b/src/gnunet/message/msg_namestore.go
index a682ef1..f03ef2c 100644
--- a/src/gnunet/message/msg_namestore.go
+++ b/src/gnunet/message/msg_namestore.go
@@ -52,7 +52,7 @@ func newGenericNamestoreMsg(size uint16, mtype enums.MsgType) 
GenericNamestoreMs
 type NamestoreZoneIterStartMsg struct {
        GenericNamestoreMsg
 
-       ZoneKey *crypto.ZonePrivate // private zone key
+       ZoneKey *crypto.ZonePrivate `init:"Init"` // private zone key
 }
 
 // NewNamecacheCacheMsg creates a new default message.
@@ -63,6 +63,9 @@ func NewNamestoreZoneIterStartMsg(zone *crypto.ZonePrivate) 
*NamestoreZoneIterSt
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamestoreZoneIterStartMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *NamestoreZoneIterStartMsg) String() string {
        return fmt.Sprintf("NamestoreZoneIterStartMsg{id=%d,zone=%s}", m.ID, 
m.ZoneKey.ID())
@@ -82,6 +85,9 @@ func NewNamestoreZoneIterNextMsg() *NamestoreZoneIterNextMsg {
        return &NamestoreZoneIterNextMsg{}
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *NamestoreZoneIterNextMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *NamestoreZoneIterNextMsg) String() string {
        return fmt.Sprintf("NamestoreZoneIterNextMsg{id=%d,limit=%d}", m.ID, 
m.Limit)
@@ -95,6 +101,42 @@ type NamestoreZoneIterStopMsg struct {
        GenericNamestoreMsg
 }
 
+//----------------------------------------------------------------------
+// MSG_NAMESTORE_RECORD_RESULT
+//----------------------------------------------------------------------
+
+type NamestoreRecordResultMsg struct {
+       GenericNamestoreMsg
+
+       Expire   util.AbsoluteTime   ``               // expiration date
+       NameLen  uint16              `order:"big"`    // length of name
+       RdLen    uint16              `order:"big"`    // size of record data
+       RdCount  uint16              `order:"big"`    // number of records
+       Reserved uint16              `order:"big"`    // alignment
+       ZoneKey  *crypto.ZonePrivate `init:"Init"`    // private zone key
+       Name     []byte              `size:"NameLen"` // name string
+       Records  []byte              `size:"RdLen"`   // serialized record data
+}
+
+func NewNamestoreRecordResultMsg(zk *crypto.ZonePrivate, label string) 
*NamestoreRecordResultMsg {
+       return &NamestoreRecordResultMsg{
+               Expire:  util.AbsoluteTimeNever(),
+               ZoneKey: zk,
+               NameLen: uint16(len(label)),
+               Name:    []byte(label),
+               RdLen:   0,
+               RdCount: 0,
+       }
+}
+
+// Init called after unmarshalling a message to setup internal state
+func (m *NamestoreRecordResultMsg) Init() error { return nil }
+
+// String returns a human-readable representation of the message.
+func (m *NamestoreRecordResultMsg) String() string {
+       return 
fmt.Sprintf("NamestoreRecordResultMsg{id=%d,zone=%s,label='%s'}", m.ID, 
m.ZoneKey.ID(), string(m.Name))
+}
+
 //----------------------------------------------------------------------
 //----------------------------------------------------------------------
 
@@ -154,19 +196,6 @@ type NamestoreZoneToNameRespMsg struct {
        Records []byte              `size:"RdLen"`   // serialized record data
 }
 
-type NamestoreRecordResultMsg struct {
-       GenericNamestoreMsg
-
-       Expire   util.AbsoluteTime   ``            // expiration date
-       NameLen  uint16              `order:"big"` // length of name
-       RdLen    uint16              `order:"big"` // size of record data
-       RdCount  uint16              `order:"big"` // number of records
-       Reserved uint16              `order:"big"` // alignment
-       ZoneKey  *crypto.ZonePrivate // private zone key
-       Name     []byte              `size:"NameLen"` // name string
-       Records  []byte              `size:"RdLen"`   // serialized record data
-}
-
 type NamestoreTxControlMsg struct {
        GenericNamestoreMsg
 
diff --git a/src/gnunet/message/msg_revocation.go 
b/src/gnunet/message/msg_revocation.go
index aaef0d4..1fa891e 100644
--- a/src/gnunet/message/msg_revocation.go
+++ b/src/gnunet/message/msg_revocation.go
@@ -34,7 +34,7 @@ import (
 type RevocationQueryMsg struct {
        MsgHeader
        Reserved uint32          `order:"big"` // Reserved for future use
-       Zone     *crypto.ZoneKey // Zone that is to be checked for revocation
+       Zone     *crypto.ZoneKey `init:"Init"` // Zone that is to be checked 
for revocation
 }
 
 // NewRevocationQueryMsg creates a new message for a given zone.
@@ -46,6 +46,9 @@ func NewRevocationQueryMsg(zkey *crypto.ZoneKey) 
*RevocationQueryMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *RevocationQueryMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *RevocationQueryMsg) String() string {
        return fmt.Sprintf("RevocationQueryMsg{zone=%s}", m.Zone.ID())
@@ -73,6 +76,9 @@ func NewRevocationQueryResponseMsg(revoked bool) 
*RevocationQueryResponseMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *RevocationQueryResponseMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *RevocationQueryResponseMsg) String() string {
        return fmt.Sprintf("RevocationQueryResponseMsg{valid=%d}", m.Valid)
@@ -88,7 +94,7 @@ type RevocationRevokeMsg struct {
        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
+       ZoneKeySig *crypto.ZoneSignature `init:"Init"`           // public zone 
key (with signature) to be revoked
 }
 
 // NewRevocationRevokeMsg creates a new message for a given zone.
@@ -102,6 +108,9 @@ func NewRevocationRevokeMsg(zsig *crypto.ZoneSignature) 
*RevocationRevokeMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *RevocationRevokeMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *RevocationRevokeMsg) String() string {
        return fmt.Sprintf("RevocationRevokeMsg{zone=%s,expire=%s}",
@@ -130,6 +139,9 @@ func NewRevocationRevokeResponseMsg(success bool) 
*RevocationRevokeResponseMsg {
        }
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *RevocationRevokeResponseMsg) Init() error { return nil }
+
 // String returns a human-readable representation of the message.
 func (m *RevocationRevokeResponseMsg) String() string {
        return fmt.Sprintf("RevocationRevokeResponseMsg{success=%v}", m.Success 
== 1)
diff --git a/src/gnunet/message/msg_transport.go 
b/src/gnunet/message/msg_transport.go
index 5369ca7..e02e3e6 100644
--- a/src/gnunet/message/msg_transport.go
+++ b/src/gnunet/message/msg_transport.go
@@ -57,6 +57,9 @@ func (m *TransportTCPWelcomeMsg) String() string {
        return fmt.Sprintf("TransportTcpWelcomeMsg{peer=%s}", m.PeerID)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *TransportTCPWelcomeMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_PING
 //
@@ -105,6 +108,9 @@ func (m *TransportPingMsg) String() string {
                m.Target, a, m.Challenge)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *TransportPingMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_PONG
 //
@@ -206,6 +212,9 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey) 
(bool, error) {
        return pub.EdVerify(data, sig)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *TransportPongMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_ACK
 //----------------------------------------------------------------------
@@ -227,6 +236,9 @@ func (m *SessionAckMsg) String() string {
        return "SessionAck{}"
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionAckMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_SYN
 //----------------------------------------------------------------------
@@ -252,6 +264,9 @@ func (m *SessionSynMsg) String() string {
        return fmt.Sprintf("SessionSyn{timestamp=%s}", m.Timestamp)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionSynMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_SYN_ACK
 //----------------------------------------------------------------------
@@ -277,6 +292,9 @@ func (m *SessionSynAckMsg) String() string {
        return fmt.Sprintf("SessionSynAck{timestamp=%s}", m.Timestamp)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionSynAckMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_QUOTA
 //----------------------------------------------------------------------
@@ -303,6 +321,9 @@ func (m *SessionQuotaMsg) String() string {
        return fmt.Sprintf("SessionQuotaMsg{%sB/s}", 
util.Scale1024(uint64(m.Quota)))
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionQuotaMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_KEEPALIVE
 //----------------------------------------------------------------------
@@ -327,6 +348,9 @@ func (m *SessionKeepAliveMsg) String() string {
        return fmt.Sprintf("SessionKeepAliveMsg{%d}", m.Nonce)
 }
 
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionKeepAliveMsg) Init() error { return nil }
+
 //----------------------------------------------------------------------
 // TRANSPORT_SESSION_KEEPALIVE_RESPONSE
 //----------------------------------------------------------------------
@@ -350,3 +374,6 @@ func NewSessionKeepAliveRespMsg(nonce uint32) 
*SessionKeepAliveRespMsg {
 func (m *SessionKeepAliveRespMsg) String() string {
        return fmt.Sprintf("SessionKeepAliveRespMsg{%d}", m.Nonce)
 }
+
+// Init called after unmarshalling a message to setup internal state
+func (m *SessionKeepAliveRespMsg) Init() error { return nil }
diff --git a/src/gnunet/service/connection.go b/src/gnunet/service/connection.go
index fbc24d0..a008bdb 100644
--- a/src/gnunet/service/connection.go
+++ b/src/gnunet/service/connection.go
@@ -134,6 +134,9 @@ func (s *Connection) Receive(ctx context.Context) 
(message.Message, error) {
        if err = data.Unmarshal(msg, s.buf[:mh.MsgSize]); err != nil {
                return nil, err
        }
+       if err = msg.Init(); err != nil {
+               return nil, err
+       }
        return msg, nil
 }
 
diff --git a/src/gnunet/service/dht/blocks/gns.go 
b/src/gnunet/service/dht/blocks/gns.go
index 0c32085..b504bc5 100644
--- a/src/gnunet/service/dht/blocks/gns.go
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -235,7 +235,7 @@ func NewRecordSet() *RecordSet {
        return &RecordSet{
                Count:   0,
                Records: make([]*ResourceRecord, 0),
-               Padding: make([]byte, 0),
+               Padding: nil,
        }
 }
 
@@ -273,6 +273,11 @@ func (rs *RecordSet) Expire() util.AbsoluteTime {
 
 // Bytes returns the binary representation
 func (rs *RecordSet) Bytes() []byte {
+       // make sure padding exists
+       if rs.Padding == nil {
+               rs.SetPadding()
+       }
+       // unmarshal record set
        buf, err := data.Marshal(rs)
        if err != nil {
                return nil
diff --git a/src/gnunet/service/dht/blocks/gns_test.go 
b/src/gnunet/service/dht/blocks/gns_test.go
index 260d83b..bc1703b 100644
--- a/src/gnunet/service/dht/blocks/gns_test.go
+++ b/src/gnunet/service/dht/blocks/gns_test.go
@@ -31,41 +31,41 @@ import (
 
 func TestGNSBlock(t *testing.T) {
        var (
-               ZONEKEY = 
"000G054G4G3HWZP2WFNVS1XJ4VXWY85G49AVYBZ7TV4EWP5J5V59H5QN40"
+               ZONEKEY = 
"000G051J6AZ48NAJP94HD6CNBXNN9C85YT8GYVVXFKGY2516YGF7HRXKR4"
                LABEL   = "@"
 
                QKEY = []byte{
-                       0xb6, 0x48, 0xfd, 0x0c, 0x4a, 0x6c, 0xaa, 0x87,
-                       0x33, 0x2f, 0xf5, 0x12, 0x90, 0xe4, 0xbd, 0x55,
-                       0x0f, 0x8c, 0xe7, 0x9b, 0xc9, 0x5b, 0x3a, 0xfb,
-                       0xbb, 0xe2, 0xd7, 0x33, 0xbc, 0x32, 0xc9, 0x7d,
-                       0xc5, 0x4a, 0x56, 0x22, 0xbf, 0xfa, 0x49, 0x1a,
-                       0x60, 0xd6, 0xdb, 0x77, 0x5d, 0x3d, 0x18, 0x99,
-                       0x5b, 0x4f, 0xc3, 0x7d, 0x86, 0x00, 0x15, 0x76,
-                       0x42, 0x03, 0x98, 0xcc, 0xdf, 0x83, 0x4d, 0x21,
+                       0xb5, 0xbb, 0xf8, 0x43, 0xfa, 0x6f, 0x7d, 0x53,
+                       0xb9, 0x84, 0x9a, 0xa1, 0x61, 0xc9, 0x44, 0x4f,
+                       0x29, 0x68, 0x98, 0x02, 0x36, 0x26, 0xc5, 0xd2,
+                       0xd3, 0x06, 0x21, 0x99, 0xf7, 0x39, 0x06, 0x46,
+                       0xdb, 0x32, 0x07, 0xa6, 0x0d, 0xd2, 0xea, 0x03,
+                       0xce, 0x74, 0x93, 0x86, 0x78, 0x3a, 0x70, 0xaa,
+                       0xa0, 0x75, 0xf1, 0x18, 0xf7, 0xb3, 0xc6, 0x0b,
+                       0x12, 0x98, 0xbb, 0x1b, 0x58, 0x53, 0xe6, 0x87,
                }
                BLK = []byte{
-                       0x00, 0x01, 0x00, 0x14, 0xe0, 0x6b, 0xea, 0x2b,
-                       0x1b, 0xd6, 0xc6, 0x9a, 0xd4, 0x30, 0xa5, 0x0f,
-                       0x81, 0x16, 0x89, 0xe1, 0x9f, 0xca, 0x1f, 0x86,
-                       0x3f, 0x83, 0x6e, 0xe6, 0xa7, 0x54, 0x97, 0xde,
-                       0xf2, 0xc4, 0x2a, 0x84, 0xb6, 0x89, 0xe6, 0x7e,
-                       0xff, 0x0c, 0xae, 0x84, 0xe6, 0xb1, 0x6c, 0x72,
-                       0x83, 0x09, 0x68, 0x5b, 0x2f, 0xa2, 0x9f, 0xbe,
-                       0xfa, 0xef, 0x43, 0x52, 0x20, 0x48, 0xe5, 0x57,
-                       0x1e, 0x65, 0x21, 0x86, 0xd4, 0x9f, 0x96, 0x51,
-                       0x4f, 0xa9, 0x6d, 0xa9, 0x98, 0xaa, 0x2d, 0xf6,
-                       0x92, 0xd7, 0x86, 0x36, 0xc0, 0x84, 0x90, 0x00,
-                       0x42, 0x2e, 0x4e, 0xc1, 0xaf, 0x6f, 0xe0, 0x7e,
-                       0x71, 0xe3, 0xc4, 0x0d, 0x00, 0x00, 0x00, 0x10,
-                       0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x08, 0x00,
-                       0xb5, 0x99, 0x2a, 0x00, 0xd0, 0x7a, 0x2b, 0x9e,
-                       0x02, 0x45, 0x54, 0x0d, 0x65, 0x26, 0xa1, 0x05,
-                       0x80, 0x26, 0xce, 0xc2, 0x70, 0xd5, 0x22, 0x38,
-                       0x80, 0x9a, 0xed, 0x63, 0x2f, 0x96, 0x60, 0x4d,
-                       0x02, 0x59, 0xd0, 0x9a, 0x4e, 0x71, 0xfa, 0x30,
-                       0xd6, 0xf9, 0xf4, 0x84, 0x5d, 0xb8, 0x60, 0xa4,
-                       0xdf, 0xea, 0x34, 0x06, 0x3f, 0x6f, 0x76, 0x9e,
+                       0x00, 0x01, 0x00, 0x14, 0x64, 0x0e, 0x2f, 0x4b,
+                       0x8e, 0x7e, 0x7f, 0x54, 0x43, 0xb4, 0x7c, 0xdc,
+                       0x84, 0xd4, 0x89, 0xfc, 0x87, 0x6e, 0x08, 0x6c,
+                       0xd9, 0x55, 0xab, 0xc6, 0x0a, 0x7d, 0xfe, 0x1b,
+                       0xfe, 0x88, 0xa7, 0x13, 0x72, 0x2b, 0xfa, 0xb1,
+                       0x70, 0xc4, 0x8c, 0xeb, 0xe3, 0x2b, 0x08, 0x9f,
+                       0x07, 0xea, 0x77, 0x51, 0x2d, 0xf1, 0x02, 0x89,
+                       0x73, 0xa8, 0xd0, 0xe0, 0x94, 0x2a, 0x16, 0x05,
+                       0xdb, 0x06, 0x0e, 0xd4, 0x0f, 0xdc, 0xac, 0x48,
+                       0xde, 0x05, 0x2c, 0x0f, 0x29, 0x81, 0xd6, 0x70,
+                       0x17, 0x95, 0x76, 0x79, 0x75, 0xce, 0x0d, 0x4b,
+                       0x98, 0xd7, 0xe5, 0x44, 0x70, 0x3b, 0xbd, 0x50,
+                       0xba, 0xfa, 0x5d, 0x01, 0x00, 0x00, 0x00, 0x10,
+                       0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x09, 0x70,
+                       0x48, 0xa4, 0xa2, 0x00, 0x45, 0xb0, 0xd0, 0xdc,
+                       0x1d, 0x60, 0xd8, 0x1c, 0x4f, 0xd3, 0x50, 0xc4,
+                       0x73, 0x20, 0xc6, 0xd5, 0x28, 0x19, 0x8e, 0xa6,
+                       0x89, 0x56, 0x9f, 0x8c, 0x48, 0xf4, 0xd6, 0x76,
+                       0xf4, 0xb9, 0x70, 0xbd, 0x01, 0x2e, 0xd1, 0x4f,
+                       0x84, 0x96, 0x1b, 0xbf, 0x6c, 0xe4, 0xdb, 0x7a,
+                       0x32, 0xc1, 0x50, 0xc4, 0xb1, 0x6b, 0x08, 0x59,
                }
        )
        // unmarshal block
@@ -89,7 +89,10 @@ func TestGNSBlock(t *testing.T) {
        query := NewGNSQuery(zk, LABEL)
 
        // check query key
-       if !bytes.Equal(QKEY, query.Key().Data) {
+       qkey := query.Key().Data
+       if !bytes.Equal(QKEY, qkey) {
+               t.Logf("expected: %s", hex.EncodeToString(QKEY))
+               t.Logf("got: %s", hex.EncodeToString(qkey))
                t.Fatal("query key mismatch")
        }
 
@@ -125,11 +128,11 @@ func TestGNSBlock(t *testing.T) {
 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,
+                       // PKEY private scalar (clamped little-endian)
+                       0x98, 0xfd, 0xfa, 0x25, 0x79, 0x90, 0xfa, 0x50,
+                       0xd4, 0xe7, 0xc8, 0x78, 0x21, 0xa0, 0x71, 0x21,
+                       0x95, 0xe5, 0x85, 0x97, 0x90, 0x96, 0x73, 0xf3,
+                       0xdf, 0xea, 0xef, 0xa4, 0x52, 0xb6, 0xd7, 0x50,
                }
                ZKEY = []byte{
                        // zone type
diff --git a/src/gnunet/service/namecache/module.go 
b/src/gnunet/service/namecache/module.go
index 0e8053a..ee3a325 100644
--- a/src/gnunet/service/namecache/module.go
+++ b/src/gnunet/service/namecache/module.go
@@ -77,7 +77,7 @@ func (m *Module) Get(ctx context.Context, query 
*blocks.GNSQuery) (block *blocks
                return
        }
        if len(e) != 1 {
-               err = errors.New("only one DHT entry exppected")
+               err = errors.New("only one namecache entry expected")
        } else {
                err = blocks.Unwrap(e[0].Blk, block)
        }
diff --git a/src/gnunet/service/revocation/pow_test.go 
b/src/gnunet/service/revocation/pow_test.go
index 41c17b4..a3bd5e6 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -13,7 +13,7 @@ import (
 // Test revocation with test vector defined in the RFC draft.
 func TestRevocationRFC(t *testing.T) {
        var (
-               D     = 
"6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
+               D     = 
"70ed98b9078c47f7d5783b26ccf98b7dd55f6088d1539597fa8bf55ac032ea6f"
                ZKEY  = 
"000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
                PROOF = "" +
                        "0005d66da3598127" +
@@ -86,7 +86,11 @@ func TestRevocationRFC(t *testing.T) {
        if err = data.Unmarshal(revData, revD); err != nil {
                t.Fatal(err)
        }
-       if !bytes.Equal(revData.ZoneKeySig.Bytes(), zkey) {
+       if err = revData.ZoneKeySig.Init(); err != nil {
+               t.Fatal(err)
+       }
+       // check sigature
+       if !bytes.Equal(revData.ZoneKeySig.ZoneKey.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")
diff --git a/src/gnunet/service/store/store_zonemaster.go 
b/src/gnunet/service/store/store_zonemaster.go
index 44b1a68..1338c01 100644
--- a/src/gnunet/service/store/store_zonemaster.go
+++ b/src/gnunet/service/store/store_zonemaster.go
@@ -32,11 +32,15 @@ import (
 )
 
 //============================================================
-// Local zone records stored in SQLite3 database
+// Local identities and zone records (SQLite3 database)
+// Identities are named ZonePrivate keys that are associated
+// with a GNUnet subsystem (like GNS, CADET and others).
+// Identities for the subsystem "gns" are called zones and
+// are collections of labeled resource record sets. All other
+// identities are usuall called "egos".
 //============================================================
 
 // Zone is the definition of a local GNS zone
-// and is stored in a SQL database for faster access.
 type Zone struct {
        ID       int64               // database identifier
        Name     string              // zone name
@@ -59,6 +63,23 @@ func NewZone(name string, sk *crypto.ZonePrivate) *Zone {
 
 //----------------------------------------------------------------------
 
+// Identity is a Zone associated with a service
+type Identity struct {
+       Zone
+
+       Svc string // associated service
+}
+
+// NewIdentity creates an initialize instance for database access
+func NewIdentity(name string, sk *crypto.ZonePrivate, svc string) *Identity {
+       return &Identity{
+               Zone: *NewZone(name, sk),
+               Svc:  svc,
+       }
+}
+
+//----------------------------------------------------------------------
+
 type Label struct {
        ID       int64             // database id of label
        Zone     int64             // database ID of parent zone
@@ -136,7 +157,7 @@ func OpenZoneDB(fname string) (db *ZoneDB, err error) {
                return
        }
        // check for initialized database
-       res := db.conn.QueryRow("select name from sqlite_master where 
type='table' and name='zones'")
+       res := db.conn.QueryRow("select name from sqlite_master where 
type='table' and name='identities'")
        var s string
        if res.Scan(&s) != nil {
                // initialize database
@@ -152,6 +173,152 @@ func (db *ZoneDB) Close() error {
        return db.conn.Close()
 }
 
+//----------------------------------------------------------------------
+// Identity handling
+//----------------------------------------------------------------------
+
+// SetIdentity inserts, updates or deletes a zone in the database.
+// The function does not change timestamps which are in the
+// responsibility of the caller.
+//   - insert: Identity.ID is nil (0)
+//   - update: Identity.Name is set
+//   - remove: otherwise
+func (db *ZoneDB) SetIdentity(id *Identity) error {
+       // GNS zones are handled by Zone instances
+       if id.Svc == "gns" {
+               return db.SetZone(&id.Zone)
+       }
+       // check for identity insert
+       if id.ID == 0 {
+               stmt := "insert into 
identities(svc,name,created,modified,ztype,zdata) values(?,?,?,?,?,?)"
+               result, err := db.conn.Exec(stmt,
+                       id.Svc, id.Name, id.Created.Val, id.Modified.Val, 
id.Key.Type, id.Key.KeyData)
+               if err != nil {
+                       return err
+               }
+               id.ID, err = result.LastInsertId()
+               return err
+       }
+       // check for identity update (name and service only only)
+       if len(id.Name) > 0 {
+               stmt := "update identities set svc=?,name=?,modified=? where 
id=?"
+               result, err := db.conn.Exec(stmt, id.Svc, id.Name, 
id.Modified.Val, id.ID)
+               if err != nil {
+                       return err
+               }
+               var num int64
+               if num, err = result.RowsAffected(); err == nil {
+                       if num != 1 {
+                               err = errors.New("update identity failed")
+                       }
+               }
+               return err
+       }
+       // remove identity from database
+       _, err := db.conn.Exec("delete from identities where id=?", id.ID)
+       return err
+}
+
+// GetIdentity gets an identifier with given database id
+func (db *ZoneDB) GetIdentity(id int64) (ident *Identity, err error) {
+       // assemble identity from database row
+       stmt := "select svc,name,created,modified,ztype,zdata from identities 
where id=?"
+       ident = new(Identity)
+       ident.ID = id
+       row := db.conn.QueryRow(stmt, id)
+       var ztype enums.GNSType
+       var zdata []byte
+       if err = row.Scan(&ident.Svc, &ident.Name, &ident.Created.Val, 
&ident.Modified.Val, &ztype, &zdata); err == nil {
+               // reconstruct private zone key
+               ident.Key, err = crypto.NewZonePrivate(ztype, zdata)
+       }
+       return
+}
+
+// GetIdentity gets an identifier with given (name,svc)
+func (db *ZoneDB) GetIdentityByName(name, svc string) (ident *Identity, err 
error) {
+       // assemble identity from database row
+       var row *sql.Row
+       stmt := "select id,created,modified,ztype,zdata from identities where 
name=?"
+       if len(svc) > 0 {
+               stmt += " and svc=?"
+               row = db.conn.QueryRow(stmt, name, svc)
+       } else {
+               row = db.conn.QueryRow(stmt, name)
+       }
+       ident = new(Identity)
+       ident.Name = name
+       ident.Svc = svc
+       var ztype enums.GNSType
+       var zdata []byte
+       if err = row.Scan(&ident.ID, &ident.Created.Val, &ident.Modified.Val, 
&ztype, &zdata); err == nil {
+               // reconstruct private zone key
+               ident.Key, err = crypto.NewZonePrivate(ztype, zdata)
+       }
+       return
+}
+
+func (db *ZoneDB) GetIdentities(filter string, args ...any) (list []*Identity, 
err error) {
+       // assemble query
+       stmt := "select id,name,svc,created,modified,ztype,zdata from 
identities"
+       if len(filter) > 0 {
+               stmt += " where " + fmt.Sprintf(filter, args...)
+       }
+       // select zones
+       var rows *sql.Rows
+       if rows, err = db.conn.Query(stmt); err != nil {
+               return
+       }
+       // process zones
+       defer rows.Close()
+       for rows.Next() {
+               // assemble identity from database row
+               i := new(Identity)
+               var ztype enums.GNSType
+               var zdata []byte
+               if err = rows.Scan(&i.ID, &i.Name, &i.Svc, &i.Created.Val, 
&i.Modified.Val, &ztype, &zdata); err != nil {
+                       // terminate on error; return list so far
+                       return
+               }
+               // reconstruct private key
+               if i.Key, err = crypto.NewZonePrivate(ztype, zdata); err != nil 
{
+                       return
+               }
+               // append to result list
+               list = append(list, i)
+       }
+       return
+}
+
+func (db *ZoneDB) GetDefaultIdentity(svc string) (ident *Identity, err error) {
+       // assemble identity from database row
+       stmt := "select id,name,created,modified,ztype,zdata from v_defaults 
where svc=?"
+       row := db.conn.QueryRow(stmt, svc)
+       ident = new(Identity)
+       ident.Svc = svc
+       var ztype enums.GNSType
+       var zdata []byte
+       if err = row.Scan(&ident.ID, &ident.Name, &ident.Created.Val, 
&ident.Modified.Val, &ztype, &zdata); err == nil {
+               // reconstruct private zone key
+               ident.Key, err = crypto.NewZonePrivate(ztype, zdata)
+       }
+       return
+}
+
+func (db *ZoneDB) SetDefaultIdentity(zk *crypto.ZonePrivate, svc string) (err 
error) {
+       // get database id of identity
+       stmt := "select id from identities where zdata=?"
+       row := db.conn.QueryRow(stmt, zk.KeyData)
+       var id int64
+       if err = row.Scan(&id); err != nil {
+               return
+       }
+       // set default
+       stmt = "insert into defaults(svc,ident) values(?,?) on conflict(svc) do 
update set ident=?"
+       _, err = db.conn.Exec(stmt, svc, id, id)
+       return
+}
+
 //----------------------------------------------------------------------
 // Zone handling
 //----------------------------------------------------------------------
@@ -165,7 +332,7 @@ func (db *ZoneDB) Close() error {
 func (db *ZoneDB) SetZone(z *Zone) error {
        // check for zone insert
        if z.ID == 0 {
-               stmt := "insert into zones(name,created,modified,ztype,zdata) 
values(?,?,?,?,?)"
+               stmt := "insert into 
identities(svc,name,created,modified,ztype,zdata) values('gns',?,?,?,?,?)"
                result, err := db.conn.Exec(stmt, z.Name, z.Created.Val, 
z.Modified.Val, z.Key.Type, z.Key.KeyData)
                if err != nil {
                        return err
@@ -175,7 +342,7 @@ func (db *ZoneDB) SetZone(z *Zone) error {
        }
        // check for zone update (name only)
        if len(z.Name) > 0 {
-               stmt := "update zones set name=?,modified=? where id=?"
+               stmt := "update identities set name=?,modified=? where id=? and 
svc='gns'"
                result, err := db.conn.Exec(stmt, z.Name, z.Modified.Val, z.ID)
                if err != nil {
                        return err
@@ -202,6 +369,7 @@ func (db *ZoneDB) GetZone(id int64) (zone *Zone, err error) 
{
        // assemble zone from database row
        stmt := "select name,created,modified,ztype,zdata from zones where id=?"
        zone = new(Zone)
+       zone.ID = id
        var ztype enums.GNSType
        var zdata []byte
        row := db.conn.QueryRow(stmt, id)
@@ -329,6 +497,29 @@ func (db *ZoneDB) GetLabels(filter string, args ...any) 
(list []*Label, err erro
        return
 }
 
+func (db *ZoneDB) GetLabelIDs(zk *crypto.ZonePrivate) (list []int64, err 
error) {
+       // get zone database id
+       row := db.conn.QueryRow("select id from zones where ztype=? and 
zdata=?", zk.Type, zk.KeyData)
+       var zid int64
+       if err = row.Scan(&zid); err != nil {
+               return
+       }
+       // select all labels for zone
+       var rows *sql.Rows
+       if rows, err = db.conn.Query("select id from labels where zid=?", zid); 
err != nil {
+               return
+       }
+       defer rows.Close()
+       var id int64
+       for rows.Next() {
+               if err = rows.Scan(&id); err != nil {
+                       return
+               }
+               list = append(list, id)
+       }
+       return
+}
+
 //----------------------------------------------------------------------
 // Record handling
 //----------------------------------------------------------------------
diff --git a/src/gnunet/service/store/store_zonemaster.sql 
b/src/gnunet/service/store/store_zonemaster.sql
index 169fefd..69b8ab5 100644
--- a/src/gnunet/service/store/store_zonemaster.sql
+++ b/src/gnunet/service/store/store_zonemaster.sql
@@ -16,15 +16,37 @@
 --
 -- SPDX-License-Identifier: AGPL3.0-or-later
 
-create table zones (
+create table identities (
     id       integer primary key autoincrement,
-    name     text unique,
+    svc      text,
+    name     text,
     created  integer,
     modified integer,
-       ztype    integer,
-    zdata    blob
+    ztype    integer,
+    zdata    blob,
+    unique (svc,name)
 );
 
+create table defaults (
+    svc      text unique,
+    ident    integer references identities(id)
+);
+
+create view v_defaults as select
+    i.id as id,
+    d.svc as svc,
+    i.name as name,
+    i.created as created,
+    i.modified as modified,
+    i.ztype as ztype,
+    i.zdata as zdata
+from identities i, defaults d
+where i.id = d.ident;
+
+create view zones as select
+    id, name, created, modified, ztype, zdata
+from identities
+where svc = 'gns';
 
 create table labels (
     id       integer primary key autoincrement,
diff --git a/src/gnunet/service/zonemaster/gui.go 
b/src/gnunet/service/zonemaster/gui.go
index 3885d01..5a2f17d 100644
--- a/src/gnunet/service/zonemaster/gui.go
+++ b/src/gnunet/service/zonemaster/gui.go
@@ -21,10 +21,10 @@ package zonemaster
 import (
        "bytes"
        "context"
-       "crypto/rand"
        "embed"
        "errors"
        "fmt"
+       "gnunet/config"
        "gnunet/crypto"
        "gnunet/enums"
        "gnunet/service/gns/rr"
@@ -122,6 +122,13 @@ func (zm *ZoneMaster) startGUI(ctx context.Context) {
                "rrdata": func(t enums.GNSType, buf []byte) string {
                        return guiRRdata(t, buf)
                },
+               "tabSetList": func(num int) (list map[int]int) {
+                       list = make(map[int]int)
+                       for i := 0; i < num; i++ {
+                               list[i+1] = 2*i + 1
+                       }
+                       return
+               },
        })
        if _, err := tpl.ParseFS(fsys, "*.htpl"); err != nil {
                logger.Println(logger.ERROR, "[zonemaster] GUI templates 
failed: "+err.Error())
@@ -136,7 +143,7 @@ func (zm *ZoneMaster) startGUI(ctx context.Context) {
        router.HandleFunc("/action/{cmd}/{mode}/{id}", zm.action)
        router.HandleFunc("/", zm.dashboard)
        srv := &http.Server{
-               Addr:              zm.cfg.ZoneMaster.GUI,
+               Addr:              config.Cfg.ZoneMaster.GUI,
                ReadTimeout:       10 * time.Second,
                ReadHeaderTimeout: 5 * time.Second,
                Handler:           router,
@@ -205,17 +212,17 @@ func (zm *ZoneMaster) actionNew(w http.ResponseWriter, r 
*http.Request, mode str
        // new zone
        case "zone":
                name := r.FormValue("name")
-               // create private key
-               seed := make([]byte, 32)
-               if _, err = rand.Read(seed); err != nil {
-                       return
-               }
-               var zp *crypto.ZonePrivate
-               kt := enums.GNS_TYPE_PKEY
-               if r.FormValue("keytype") == "EDKEY" {
+               // get key type
+               var kt enums.GNSType
+               switch r.FormValue("keytype") {
+               case "EDKEY":
                        kt = enums.GNS_TYPE_EDKEY
+               case "PKEY":
+                       kt = enums.GNS_TYPE_PKEY
                }
-               zp, err = crypto.NewZonePrivate(kt, seed)
+               // cretae private key
+               var zp *crypto.ZonePrivate
+               zp, err = crypto.NewZonePrivate(kt, nil)
                if err != nil {
                        return
                }
@@ -366,7 +373,7 @@ func (zm *ZoneMaster) updRec(w http.ResponseWriter, r 
*http.Request, id int64) e
 }
 
 //----------------------------------------------------------------------
-// Create new zone. label or resource record
+// Create new zone, label or resource record
 //----------------------------------------------------------------------
 
 type NewEditData struct {
@@ -630,8 +637,15 @@ func (zm *ZoneMaster) remove(w http.ResponseWriter, r 
*http.Request) {
 // Helper methods
 //======================================================================
 
+// MainData for the template "main"
+type MainData struct {
+       Content string // Page content
+       Params  any    // reference to parameters
+       NumRR   int    // number of RR types supported
+}
+
 // render a webpage with given data and template reference
-func renderPage(w io.Writer, data interface{}, page string) {
+func renderPage(w io.Writer, data any, page string) {
        // create content section
        t := tpl.Lookup(page)
        if t == nil {
@@ -649,7 +663,11 @@ func renderPage(w io.Writer, data interface{}, page 
string) {
                _, _ = io.WriteString(w, "No main template found")
                return
        }
-       if err := t.Execute(w, content.String()); err != nil {
+       md := new(MainData)
+       md.Params = data
+       md.Content = content.String()
+       md.NumRR = len(rrtypes)
+       if err := t.Execute(w, md); err != nil {
                _, _ = io.WriteString(w, err.Error())
        }
 }
diff --git a/src/gnunet/service/zonemaster/gui.htpl 
b/src/gnunet/service/zonemaster/gui.htpl
index ffa9720..de51ee3 100644
--- a/src/gnunet/service/zonemaster/gui.htpl
+++ b/src/gnunet/service/zonemaster/gui.htpl
@@ -3,12 +3,12 @@
 <html lang="en">
     <head>
         <meta name="viewport" content="width=device-width, initial-scale=1, 
shrink-to-fit=no">
-        {{template "css"}}
+        {{template "css" .NumRR}}
     </head>
     <body>
         <h1>GNUnet Zone Master</h1>
         <hr/>
-        {{.}}
+        {{.Content}}
         <script>
             function notify(msg) {
                 if ('Notification' in window) {
diff --git a/src/gnunet/service/zonemaster/gui_css.htpl 
b/src/gnunet/service/zonemaster/gui_css.htpl
index b31e714..8a26b30 100644
--- a/src/gnunet/service/zonemaster/gui_css.htpl
+++ b/src/gnunet/service/zonemaster/gui_css.htpl
@@ -165,9 +165,6 @@
     .nested {
         display: none;
     }
-    .active {
-        display: block;
-    }
     .tabset > input[type="radio"] {
         position: absolute;
         left: -200vw;
@@ -175,26 +172,10 @@
     .tabset .tab-panel {
         display: none;
     }
-    .tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child,
-    .tabset > input:nth-child(3):checked ~ .tab-panels > 
.tab-panel:nth-child(2),
-    .tabset > input:nth-child(5):checked ~ .tab-panels > 
.tab-panel:nth-child(3),
-    .tabset > input:nth-child(7):checked ~ .tab-panels > 
.tab-panel:nth-child(4),
-    .tabset > input:nth-child(9):checked ~ .tab-panels > 
.tab-panel:nth-child(5),
-    .tabset > input:nth-child(11):checked ~ .tab-panels > 
.tab-panel:nth-child(6),
-    .tabset > input:nth-child(13):checked ~ .tab-panels > 
.tab-panel:nth-child(7),
-    .tabset > input:nth-child(15):checked ~ .tab-panels > 
.tab-panel:nth-child(8),
-    .tabset > input:nth-child(17):checked ~ .tab-panels > 
.tab-panel:nth-child(9),
-    .tabset > input:nth-child(19):checked ~ .tab-panels > 
.tab-panel:nth-child(10),
-    .tabset > input:nth-child(21):checked ~ .tab-panels > 
.tab-panel:nth-child(11),
-    .tabset > input:nth-child(23):checked ~ .tab-panels > 
.tab-panel:nth-child(12),
-    .tabset > input:nth-child(25):checked ~ .tab-panels > 
.tab-panel:nth-child(13),
-    .tabset > input:nth-child(27):checked ~ .tab-panels > 
.tab-panel:nth-child(14),
-    .tabset > input:nth-child(29):checked ~ .tab-panels > 
.tab-panel:nth-child(15),
-    .tabset > input:nth-child(31):checked ~ .tab-panels > 
.tab-panel:nth-child(16),
-    .tabset > input:nth-child(33):checked ~ .tab-panels > 
.tab-panel:nth-child(17),
-    .tabset > input:nth-child(35):checked ~ .tab-panels > 
.tab-panel:nth-child(18),
-    .tabset > input:nth-child(37):checked ~ .tab-panels > 
.tab-panel:nth-child(19),
-    .tabset > input:nth-child(39):checked ~ .tab-panels > 
.tab-panel:nth-child(20) {
+    {{range $i,$j := tabSetList .}}
+    .tabset > input:nth-child({{$j}}):checked ~ .tab-panels > 
.tab-panel:nth-child({{$i}}),
+    {{end}}
+    .active {
         display: block;
     }
     .tabset > label {
diff --git a/src/gnunet/service/zonemaster/gui_new.htpl 
b/src/gnunet/service/zonemaster/gui_new.htpl
index f470de9..81d32e8 100644
--- a/src/gnunet/service/zonemaster/gui_new.htpl
+++ b/src/gnunet/service/zonemaster/gui_new.htpl
@@ -31,7 +31,7 @@
             alert("Empty zone name not allowed");
             return false;
         }
-        for (var i = 0; i < names.length; i++) {
+        for (var i = 0; i < zone_names.length; i++) {
             if (zone_names[i] == name) {
                 alert("Zone name already used");
                 return false;
@@ -45,7 +45,7 @@
 {{define "new_label"}}
 <div>
     <h3>Creating a new GNS label for zone "{{index .Params "zone"}}":</h3>
-    <form action="/action/new/label/{{.Ref}}" 
onsubmit="return(label_validate());">
+    <form action="/action/new/label/{{.Ref}}" method="post" 
onsubmit="return(label_validate());">
         <table>
             <tr>
                 <td align="right">Name:</td>
@@ -68,7 +68,7 @@
             alert("Empty labels not allowed");
             return false;
         }
-        for (var i = 0; i < names.length; i++) {
+        for (var i = 0; i < label_names.length; i++) {
             if (label_names[i] == name) {
                 alert("Label already used");
                 return false;
diff --git a/src/gnunet/service/zonemaster/gui_rr.htpl 
b/src/gnunet/service/zonemaster/gui_rr.htpl
index 360a734..00b9148 100644
--- a/src/gnunet/service/zonemaster/gui_rr.htpl
+++ b/src/gnunet/service/zonemaster/gui_rr.htpl
@@ -1,19 +1,19 @@
 {{define "RRCommon"}}
-    <input type="hidden" name="lid" value="{{index . "lid"}}">
-    {{range $k, $v := .}}
+    <input type="hidden" name="lid" value="{{index .Params "lid"}}">
+    {{range $k, $v := .Params}}
         <input type="hidden" name="old_{{$k}}" value="{{$v}}">
     {{end}}
-    {{$pf := index . "prefix"}}
+    {{$pf := index .Params "prefix"}}
     <tr>
         <td align="right" valign="top"><b>Expires:</b></td>
         <td>
             Never <input type="checkbox" class="alternate" name="{{$pf}}never"
-                {{if eq "on" (index . (print $pf 
"never"))}}checked="checked"{{end}}
+                {{if eq "on" (index .Params (print $pf 
"never"))}}checked="checked"{{end}}
             >
             <div class="alternate">
                 At given date and time:
                 <input type="datetime-local" id="{{$pf}}expires" 
name="{{$pf}}expires" required
-                    value="{{index . (print $pf "expires")}}"
+                    value="{{index .Params (print $pf "expires")}}"
                 >
             </div>
         </td>
@@ -22,13 +22,13 @@
         <td align="right" valign="top"><b>Flags:</b></td>
         <td>
             <input type="checkbox" name="{{$pf}}private"
-                {{if eq "on" (index . (print $pf 
"private"))}}checked="checked" class="disabled"{{end}}
+                {{if eq "on" (index .Params (print $pf 
"private"))}}checked="checked" class="disabled"{{end}}
                 > Private<br>
             <input type="checkbox" name="{{$pf}}shadow"
-                {{if eq "on" (index . (print $pf "shadow"))}}checked="checked" 
class="disabled"{{end}}
+                {{if eq "on" (index .Params (print $pf 
"shadow"))}}checked="checked" class="disabled"{{end}}
                 > Shadow<br>
             <input type="checkbox" name="{{$pf}}suppl"
-                {{if eq "on" (index . (print $pf "suppl"))}}checked="checked" 
class="disabled"{{end}}
+                {{if eq "on" (index .Params (print $pf 
"suppl"))}}checked="checked" class="disabled"{{end}}
                 > Supplemental<br>
         </td>
     </tr>
@@ -68,7 +68,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -98,7 +98,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -125,7 +125,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -145,7 +145,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -165,7 +165,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -200,7 +200,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -302,7 +302,7 @@
                     </div>
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -323,7 +323,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -344,7 +344,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -364,7 +364,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -384,7 +384,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
@@ -413,7 +413,7 @@
                     >
                 </td>
             </tr>
-            {{template "RRCommon" .Params}}
+            {{template "RRCommon" .}}
             <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
         </table>
     </form>
diff --git a/src/gnunet/service/zonemaster/records.go 
b/src/gnunet/service/zonemaster/records.go
index 3015893..a10ee05 100644
--- a/src/gnunet/service/zonemaster/records.go
+++ b/src/gnunet/service/zonemaster/records.go
@@ -22,8 +22,11 @@ import (
        "encoding/hex"
        "errors"
        "fmt"
+       "gnunet/crypto"
        "gnunet/enums"
+       "gnunet/service/dht/blocks"
        "gnunet/service/gns/rr"
+       "gnunet/service/store"
        "gnunet/util"
        "net"
        "time"
@@ -330,7 +333,7 @@ func Map2RRData(t enums.GNSType, set map[string]string) 
(buf []byte, err error)
 }
 
 //======================================================================
-// Get list of allowed new RRs given a set of existing RRs.
+// ResourceRecord helpers
 //======================================================================
 
 // Create a list of compatible record types from list of
@@ -346,3 +349,23 @@ func compatibleRR(in []*enums.GNSSpec, label string) (out 
[]*enums.GNSSpec) {
        }
        return
 }
+
+// get a list of resource records for a given label in a zone.
+func (zm *ZoneMaster) getRecords(zk *crypto.ZoneKey, label int64) (rs 
*blocks.RecordSet, expire util.AbsoluteTime, err error) {
+       // collect records for zone label
+       var recs []*store.Record
+       if recs, err = zm.zdb.GetRecords("lid=%d", label); err != nil {
+               return
+       }
+       // assemble record set and find earliest expiration
+       expire = util.AbsoluteTimeNever()
+       rs = blocks.NewRecordSet()
+       for _, r := range recs {
+               if r.Expire.Compare(expire) < 0 {
+                       expire = r.Expire
+               }
+               rs.AddRecord(&r.ResourceRecord)
+       }
+       // do not add padding yet as record set may be filtered before use.
+       return
+}
diff --git a/src/gnunet/service/zonemaster/rpc.go 
b/src/gnunet/service/zonemaster/rpc.go
index 2060e56..4df5b21 100644
--- a/src/gnunet/service/zonemaster/rpc.go
+++ b/src/gnunet/service/zonemaster/rpc.go
@@ -20,5 +20,5 @@ package zonemaster
 
 import "gnunet/service"
 
-func (s *Service) InitRPC(rpc *service.JRPCServer) {
+func (zm *ZoneMaster) InitRPC(rpc *service.JRPCServer) {
 }
diff --git a/src/gnunet/service/zonemaster/service.go 
b/src/gnunet/service/zonemaster/service.go
index c73857f..5725ec8 100644
--- a/src/gnunet/service/zonemaster/service.go
+++ b/src/gnunet/service/zonemaster/service.go
@@ -25,7 +25,7 @@ import (
 
        "gnunet/config"
        "gnunet/core"
-       "gnunet/crypto"
+       "gnunet/enums"
        "gnunet/message"
        "gnunet/service"
        "gnunet/service/dht/blocks"
@@ -35,43 +35,20 @@ import (
        "github.com/bfix/gospel/logger"
 )
 
-type ZoneIterator struct {
-       zk *crypto.ZonePrivate
-}
-
 //----------------------------------------------------------------------
-// "GNUnet Zonemaster" service implementation:
-// The zonemaster service handles Namestore messages
+// "GNUnet Zonemaster" socket service implementation:
+// Zonemaster handles Namestore and Identity messages.
 //----------------------------------------------------------------------
 
-// Service implements a GNS service
-type Service struct {
-       Module
-
-       ZoneIters *util.Map[uint32, *ZoneIterator]
-}
-
-// NewService creates a new GNS service instance
-func NewService(ctx context.Context, c *core.Core) service.Service {
-       // instantiate service
-       mod := NewModule(ctx, c)
-       srv := &Service{
-               Module:    *mod,
-               ZoneIters: util.NewMap[uint32, *ZoneIterator](),
-       }
-       // set external function references (external services)
-       srv.StoreLocal = srv.StoreNamecache
-       srv.StoreRemote = srv.StoreDHT
-
-       return srv
-}
-
 // ServeClient processes a client channel.
-func (s *Service) ServeClient(ctx context.Context, id int, mc 
*service.Connection) {
+func (zm *ZoneMaster) ServeClient(ctx context.Context, id int, mc 
*service.Connection) {
        reqID := 0
        var cancel context.CancelFunc
        ctx, cancel = context.WithCancel(ctx)
 
+       // inform sub-service about new session
+       zm.identity.NewSession(id, mc)
+
        for {
                // receive next message from client
                reqID++
@@ -89,33 +66,158 @@ func (s *Service) ServeClient(ctx context.Context, id int, 
mc *service.Connectio
                }
                logger.Printf(logger.INFO, "[zonemaster:%d:%d] Received 
request: %v\n", id, reqID, msg)
 
+               // context with values
+               values := make(util.ParameterSet)
+               values["id"] = id
+               values["label"] = fmt.Sprintf(":%d:%d", id, reqID)
+               valueCtx := context.WithValue(ctx, core.CtxKey("params"), 
values)
+
                // handle message
-               valueCtx := context.WithValue(ctx, core.CtxKey("label"), 
fmt.Sprintf(":%d:%d", id, reqID))
-               s.HandleMessage(valueCtx, nil, msg, mc)
+               zm.HandleMessage(valueCtx, nil, msg, mc)
        }
+       // inform sub.services about closed session
+       zm.identity.CloseSession(id)
+
        // close client connection
        mc.Close()
 
        // cancel all tasks running for this session/connection
-       logger.Printf(logger.INFO, "[zonemaster:%d] Start closing 
session...\n", id)
+       logger.Printf(logger.INFO, "[zonemaster:%d] Closing session...\n", id)
        cancel()
 }
 
 // Handle a single incoming message
-func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg 
message.Message, back transport.Responder) bool {
+func (zm *ZoneMaster) HandleMessage(ctx context.Context, sender *util.PeerID, 
msg message.Message, back transport.Responder) bool {
        // assemble log label
-       label := ""
-       if v := ctx.Value("label"); v != nil {
-               label, _ = v.(string)
+       var id int
+       var label string
+       if v := ctx.Value(core.CtxKey("params")); v != nil {
+               if ps, ok := v.(util.ParameterSet); ok {
+                       label, _ = util.GetParam[string](ps, "label")
+                       id, _ = util.GetParam[int](ps, "id")
+               }
        }
        // perform lookup
        switch m := msg.(type) {
 
+       //------------------------------------------------------------------
+       // Identity service
+       //------------------------------------------------------------------
+
+       // start identity update listener
+       case *message.IdentityStartMsg:
+               if err := zm.identity.Start(ctx, id); err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
session for %d failed: %v\n", label, id, err)
+                       return false
+               }
+
+       // create a new identity with given private key
+       case *message.IdentityCreateMsg:
+               if err := zm.identity.Create(ctx, id, m.ZoneKey, m.Name()); err 
!= nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
create failed: %v\n", label, err)
+                       return false
+               }
+
+       // rename identity
+       case *message.IdentityRenameMsg:
+               id, err := zm.zdb.GetIdentityByName(m.OldName(), 
IDENT_DEFAULT_SERVICE)
+               if err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
lookup failed: %v\n", label, err)
+                       return false
+               }
+               // change name
+               id.Name = m.NewName()
+               err = zm.zdb.SetIdentity(id)
+
+               // send response
+               rc := enums.RC_OK
+               msg := ""
+               if err != nil {
+                       rc = enums.RC_NO
+                       msg = err.Error()
+               }
+               resp := message.NewIdentityResultCodeMsg(rc, msg)
+               if err = back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%s] Can't send 
response (%v): %v\n", label, resp, err)
+               }
+
+       // delete identity
+       case *message.IdentityDeleteMsg:
+               id, err := zm.zdb.GetIdentityByName(m.Name(), 
IDENT_DEFAULT_SERVICE)
+               if err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
lookup failed: %v\n", label, err)
+                       return false
+               }
+               // delete in database
+               id.Name = ""
+               err = zm.zdb.SetIdentity(id)
+
+               // send response
+               rc := enums.RC_OK
+               msg := ""
+               if err != nil {
+                       rc = enums.RC_NO
+                       msg = err.Error()
+               }
+               resp := message.NewIdentityResultCodeMsg(rc, msg)
+               if err = back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%s] Can't send 
response (%v): %v\n", label, resp, err)
+               }
+
+       // lookup identity
+       case *message.IdentityLookupMsg:
+               id, err := zm.zdb.GetIdentityByName(m.Name, 
IDENT_DEFAULT_SERVICE)
+               if err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
lookup failed: %v\n", label, err)
+                       return false
+               }
+               resp := message.NewIdentityUpdateMsg(id.Name, id.Key)
+               logger.Printf(logger.DBG, "[identity:%s] Sending %v", label, 
resp)
+               if err = back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%s] Can't send 
response (%v): %v\n", label, resp, err)
+               }
+
+       // get default identity for service
+       case *message.IdentityGetDefaultMsg:
+               id, err := zm.zdb.GetDefaultIdentity(m.Service())
+               if err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Identity 
lookup failed: %v\n", label, err)
+                       return false
+               }
+               resp := message.NewIdentityUpdateMsg(id.Name, id.Key)
+               logger.Printf(logger.DBG, "[identity:%s] Sending %v", label, 
resp)
+               if err = back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%s] Can't send 
response (%v): %v\n", label, resp, err)
+               }
+
+       // set default identity for service
+       case *message.IdentitySetDefaultMsg:
+               err := zm.zdb.SetDefaultIdentity(m.ZoneKey, m.Service())
+
+               // send response
+               rc := enums.RC_OK
+               msg := ""
+               if err != nil {
+                       rc = enums.RC_NO
+                       msg = err.Error()
+               }
+               resp := message.NewIdentityResultCodeMsg(rc, msg)
+               if err = back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%s] Can't send 
response (%v): %v\n", label, resp, err)
+               }
+
+       //------------------------------------------------------------------
+       // Namestore service
+       //------------------------------------------------------------------
+
        // start new zone iteration
        case *message.NamestoreZoneIterStartMsg:
-               zi := new(ZoneIterator)
-               zi.zk = m.ZoneKey
-               s.ZoneIters.Put(m.ID, zi, 0)
+               iter := zm.namestore.NewIterator(m.ID, m.ZoneKey)
+               resp := iter.Next()
+               if err := back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[zonemaster%s] Can't send 
response (%v)\n", label, resp)
+                       return false
+               }
 
        default:
                //----------------------------------------------------------
@@ -128,7 +230,7 @@ func (s *Service) HandleMessage(ctx context.Context, sender 
*util.PeerID, msg me
 }
 
 // storeDHT stores a GNS block in the DHT.
-func (s *Service) StoreDHT(ctx context.Context, query blocks.Query, block 
blocks.Block) (err error) {
+func (zm *ZoneMaster) StoreDHT(ctx context.Context, query blocks.Query, block 
blocks.Block) (err error) {
        // assemble DHT request
        req := message.NewDHTP2PPutMsg(block)
        req.Flags = query.Flags()
@@ -140,7 +242,7 @@ func (s *Service) StoreDHT(ctx context.Context, query 
blocks.Query, block blocks
 }
 
 // storeNamecache stores a GNS block in the local namecache.
-func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery, 
block *blocks.GNSBlock) (err error) {
+func (zm *ZoneMaster) StoreNamecache(ctx context.Context, query 
*blocks.GNSQuery, block *blocks.GNSBlock) (err error) {
        // assemble Namecache request
        req := message.NewNamecacheCacheMsg(block)
 
diff --git a/src/gnunet/service/zonemaster/service_identity.go 
b/src/gnunet/service/zonemaster/service_identity.go
new file mode 100644
index 0000000..b7777be
--- /dev/null
+++ b/src/gnunet/service/zonemaster/service_identity.go
@@ -0,0 +1,135 @@
+// 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 zonemaster
+
+import (
+       "context"
+       "fmt"
+       "gnunet/crypto"
+       "gnunet/enums"
+       "gnunet/message"
+       "gnunet/service/store"
+       "gnunet/transport"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/logger"
+)
+
+//nolint:stylecheck // my style is my style...
+const (
+       IDENT_DEFAULT_SERVICE = "ego"
+)
+
+//----------------------------------------------------------------------
+// "GNUnet Identity" service implementation:
+//----------------------------------------------------------------------
+
+type IdentitySession struct {
+       id      int
+       updates bool
+       back    transport.Responder
+}
+
+type Identity struct{}
+
+type IdentityService struct {
+       zm      *ZoneMaster                      // reference to main service
+       clients *util.Map[int, *IdentitySession] // client sessions
+}
+
+func NewIdentityService(zm *ZoneMaster) *IdentityService {
+       srv := new(IdentityService)
+       srv.zm = zm
+       srv.clients = util.NewMap[int, *IdentitySession]()
+       return srv
+}
+
+func (ident *IdentityService) NewSession(id int, back transport.Responder) {
+       sess := &IdentitySession{
+               id:      id,
+               updates: false,
+               back:    back,
+       }
+       ident.clients.Put(id, sess, 0)
+}
+
+func (ident *IdentityService) CloseSession(id int) {
+       ident.clients.Delete(id, 0)
+}
+
+func (ident *IdentityService) FollowUpdates(id int) *IdentitySession {
+       if sess, ok := ident.clients.Get(id, 0); ok {
+               sess.updates = true
+               return sess
+       }
+       return nil
+}
+
+func (ident *IdentityService) Start(ctx context.Context, id int) (err error) {
+       // flag client as update receiver
+       sess := ident.FollowUpdates(id)
+       if sess == nil {
+               err = fmt.Errorf("no session available for client %d", id)
+               return
+       }
+       // initial update is to send all existing identites
+       var list []*store.Identity
+       if list, err = ident.zm.zdb.GetIdentities(""); err != nil {
+               return
+       }
+       for _, ident := range list {
+               resp := message.NewIdentityUpdateMsg(ident.Name, ident.Key)
+               logger.Printf(logger.DBG, "[identity:%d] Sending %v", id, resp)
+               if err = sess.back.Send(ctx, resp); err != nil {
+                       logger.Printf(logger.ERROR, "[identity:%d] Can't send 
response (%v): %v\n", id, resp, err)
+                       return
+               }
+       }
+       // terminate with EOL
+       resp := message.NewIdentityUpdateMsg("", nil)
+       if err = sess.back.Send(ctx, resp); err != nil {
+               logger.Printf(logger.ERROR, "[identity:%d] Can't send response 
(%v): %v\n", id, resp, err)
+               return
+       }
+       return
+}
+
+func (ident *IdentityService) Create(ctx context.Context, cid int, zk 
*crypto.ZonePrivate, name string) (err error) {
+       // get client session
+       sess, ok := ident.clients.Get(cid, 0)
+       if !ok {
+               err = fmt.Errorf("no session available for client %d", cid)
+               return
+       }
+       // add identity
+       id := store.NewIdentity(name, zk, IDENT_DEFAULT_SERVICE)
+       err = ident.zm.zdb.SetIdentity(id)
+       rc := enums.RC_OK
+       msg := ""
+       if err != nil {
+               rc = enums.RC_NO
+               msg = err.Error()
+       }
+       resp := message.NewIdentityResultCodeMsg(rc, msg)
+       if err = sess.back.Send(ctx, resp); err != nil {
+               logger.Printf(logger.ERROR, "[identity:%d] Can't send response 
(%v): %v\n", cid, resp, err)
+               return
+       }
+       return
+}
diff --git a/src/gnunet/service/zonemaster/service_namestore.go 
b/src/gnunet/service/zonemaster/service_namestore.go
new file mode 100644
index 0000000..2c6d7d1
--- /dev/null
+++ b/src/gnunet/service/zonemaster/service_namestore.go
@@ -0,0 +1,90 @@
+// 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 zonemaster
+
+import (
+       "gnunet/crypto"
+       "gnunet/message"
+       "gnunet/service/store"
+       "gnunet/util"
+)
+
+//----------------------------------------------------------------------
+// "GNUnet Namestore" service implementation:
+//----------------------------------------------------------------------
+
+type ZoneIterator struct {
+       id       uint32
+       zk       *crypto.ZonePrivate
+       lastUsed util.AbsoluteTime
+       db       *store.ZoneDB
+
+       labels []int64
+       pos    int
+}
+
+func NewZoneIterator(id uint32, zk *crypto.ZonePrivate, db *store.ZoneDB) (zi 
*ZoneIterator, err error) {
+       // get list of labels to handle
+       var labels []int64
+       if labels, err = db.GetLabelIDs(zk); err != nil {
+               return
+       }
+       // assemble zone iterator
+       zi = &ZoneIterator{
+               id:       id,
+               zk:       zk,
+               lastUsed: util.AbsoluteTimeNow(),
+               db:       db,
+               pos:      0,
+               labels:   labels,
+       }
+       return
+}
+
+func (zi *ZoneIterator) Next() *message.NamestoreRecordResultMsg {
+       if zi.pos == len(zi.labels)-1 {
+               // end of list reached
+               return nil
+       }
+
+       return nil
+}
+
+// NamestoreService to handle namestore requests
+type NamestoreService struct {
+       zm    *ZoneMaster
+       iters *util.Map[uint32, *ZoneIterator]
+}
+
+func NewNamestoreService(zm *ZoneMaster) *NamestoreService {
+       return &NamestoreService{
+               zm:    zm,
+               iters: util.NewMap[uint32, *ZoneIterator](),
+       }
+}
+
+func (s *NamestoreService) NewIterator(id uint32, zk *crypto.ZonePrivate) 
*ZoneIterator {
+       zi := &ZoneIterator{
+               id:       id,
+               zk:       zk,
+               lastUsed: util.AbsoluteTimeNow(),
+       }
+       s.iters.Put(id, zi, 0)
+       return zi
+}
diff --git a/src/gnunet/service/zonemaster/zonemaster.go 
b/src/gnunet/service/zonemaster/zonemaster.go
index 7c2a13c..4703d2c 100644
--- a/src/gnunet/service/zonemaster/zonemaster.go
+++ b/src/gnunet/service/zonemaster/zonemaster.go
@@ -21,6 +21,7 @@ package zonemaster
 import (
        "context"
        "gnunet/config"
+       "gnunet/core"
        "gnunet/enums"
        "gnunet/service/dht/blocks"
        "gnunet/service/store"
@@ -31,22 +32,36 @@ import (
 )
 
 //======================================================================
-// "GNS ZoneMaster" implementation:
-// Manage and publish local zone records
+// "GNS ZoneMaster" implementation (extended):
+// Manage local identities for subsystems. Manage and publish
+// local GNS zone records.
 //======================================================================
 
-// ZoneMaster instance
+// ZoneMaster implements
 type ZoneMaster struct {
-       cfg *config.Config // Zonemaster configuration
-       zdb *store.ZoneDB  // ZoneDB connection
-       srv *Service       // NameStore service
+       Module
+
+       zdb       *store.ZoneDB     // ZoneDB connection
+       namestore *NamestoreService // namestore subservice
+       identity  *IdentityService  // identity subservice
 }
 
-// NewZoneMaster initializes a new zone master instance.
-func NewZoneMaster(cfg *config.Config, srv *Service) *ZoneMaster {
-       zm := new(ZoneMaster)
-       zm.cfg = cfg
-       return zm
+// NewService initializes a new zone master service.
+func NewService(ctx context.Context, c *core.Core) *ZoneMaster {
+       mod := NewModule(ctx, c)
+       srv := &ZoneMaster{
+               Module: *mod,
+       }
+
+       // set external function references (external services)
+       srv.StoreLocal = srv.StoreNamecache
+       srv.StoreRemote = srv.StoreDHT
+
+       // instantiate sub-services
+       srv.namestore = NewNamestoreService(srv)
+       srv.identity = NewIdentityService(srv)
+
+       return srv
 }
 
 // Run zone master: connect to zone database and start the RPC/HTTP
@@ -55,12 +70,8 @@ func NewZoneMaster(cfg *config.Config, srv *Service) 
*ZoneMaster {
 func (zm *ZoneMaster) Run(ctx context.Context) {
        // connect to database
        logger.Println(logger.INFO, "[zonemaster] Connecting to zone 
database...")
-       dbFile, ok := util.GetParam[string](zm.cfg.ZoneMaster.Storage, "file")
-       if !ok {
-               logger.Printf(logger.ERROR, "[zonemaster] missing database file 
specification")
-               return
-       }
        var err error
+       dbFile, _ := util.GetParam[string](config.Cfg.ZoneMaster.Storage, 
"file")
        if zm.zdb, err = store.OpenZoneDB(dbFile); err != nil {
                logger.Printf(logger.ERROR, "[zonemaster] open database: %v", 
err)
                return
@@ -69,15 +80,15 @@ func (zm *ZoneMaster) Run(ctx context.Context) {
 
        // start HTTP GUI
        zm.startGUI(ctx)
-       /*
-               // publish on start-up
-               if err = zm.Publish(ctx); err != nil {
-                       logger.Printf(logger.ERROR, "[zonemaster] initial 
publish failed: %s", err.Error())
-                       return
-               }
-       */
+
+       // publish on start-up
+       if err = zm.Publish(ctx); err != nil {
+               logger.Printf(logger.ERROR, "[zonemaster] initial publish 
failed: %s", err.Error())
+               return
+       }
+
        // periodically publish GNS blocks to the DHT
-       tick := time.NewTicker(time.Duration(zm.cfg.ZoneMaster.Period) * 
time.Second)
+       tick := time.NewTicker(time.Duration(config.Cfg.ZoneMaster.Period) * 
time.Second)
 loop:
        for {
                select {
@@ -125,34 +136,44 @@ func (zm *ZoneMaster) PublishZoneLabel(ctx 
context.Context, zone *store.Zone, la
        zk := zone.Key.Public()
        logger.Printf(logger.INFO, "[zonemaster] Publishing label '%s' of zone 
%s", label.Name, zk.ID())
 
-       // collect public records for zone label
-       recs, err := zm.zdb.GetRecords("lid=%d and flags&%d = 0", label.ID, 
enums.GNS_FLAG_PRIVATE)
+       // collect all records for label
+       rrSet, expire, err := zm.getRecords(zk, label.ID)
        if err != nil {
                return err
        }
-       // assemble record set and find earliest expiration
-       expire := util.AbsoluteTimeNever()
-       rrSet := blocks.NewRecordSet()
-       for _, r := range recs {
-               if r.Expire.Compare(expire) < 0 {
-                       expire = r.Expire
-               }
-               rrSet.AddRecord(&r.ResourceRecord)
-       }
-       rrSet.SetPadding()
        if rrSet.Count == 0 {
                logger.Println(logger.INFO, "[zonemaster] No resource records 
-- skipped")
                return nil
        }
 
-       // assemble GNS query
+       // assemble GNS query (common for DHT and Namecache)
        query := blocks.NewGNSQuery(zk, label.Name)
 
-       // assemble, encrypt and sign GNS block
-       blk, _ := blocks.NewGNSBlock().(*blocks.GNSBlock)
+       //------------------------------------------------------------------
+       // Publish to DHT
+       //------------------------------------------------------------------
+
+       // filter out private resource records.
+       recsDHT := util.Clone(rrSet.Records)
+       num := uint32(len(recsDHT))
+       for i, rec := range recsDHT {
+               if rec.Flags&enums.GNS_FLAG_PRIVATE != 0 {
+                       copy(recsDHT[i:], recsDHT[i+1:])
+                       num--
+                       recsDHT = recsDHT[:num]
+               }
+       }
+       rrsDHT := &blocks.RecordSet{
+               Count:   num,
+               Records: recsDHT,
+               Padding: nil,
+       }
+       rrsDHT.SetPadding()
 
-       blk.Body.Expire = expire
-       blk.Body.Data, err = zk.Encrypt(rrSet.Bytes(), label.Name, expire)
+       // build block for DHT
+       blkDHT, _ := blocks.NewGNSBlock().(*blocks.GNSBlock)
+       blkDHT.Body.Expire = expire
+       blkDHT.Body.Data, err = zk.Encrypt(rrSet.Bytes(), label.Name, expire)
        if err != nil {
                return err
        }
@@ -160,19 +181,39 @@ func (zm *ZoneMaster) PublishZoneLabel(ctx 
context.Context, zone *store.Zone, la
        if err != nil {
                return err
        }
-       if err = blk.Sign(dzk); err != nil {
+       if err = blkDHT.Sign(dzk); err != nil {
+               return err
+       }
+       // publish GNS block to DHT
+       if err = zm.StoreDHT(ctx, query, blkDHT); err != nil {
                return err
        }
 
-       // DEBUG:
-       // logger.Printf(logger.DBG, "[zonemaster]  Query key = %s", 
hex.EncodeToString(query.Key().Data))
-       // logger.Printf(logger.DBG, "[zonemaster] Block data = %s", 
hex.EncodeToString(blk.Bytes()))
+       // DEBUG
+       /*
+               logger.Printf(logger.DBG, "[zonemaster] pub = %s", 
util.EncodeBinaryToString(zk.Bytes()))
+               logger.Printf(logger.DBG, "[zonemaster] query = %s", 
hex.EncodeToString(query.Key().Data))
+               logger.Printf(logger.DBG, "[zonemaster] blk = %s", 
hex.EncodeToString(blkDHT.Bytes()))
+       */
+
+       //------------------------------------------------------------------
+       // Publish to Namecache
+       //------------------------------------------------------------------
 
-       // publish GNS block to DHT and Namecache
-       if err = zm.srv.StoreDHT(ctx, query, blk); err != nil {
+       // build block for Namecache
+       blkNC, _ := blocks.NewGNSBlock().(*blocks.GNSBlock)
+       blkNC.Body.Expire = expire
+       blkNC.Body.Data = rrSet.Bytes()
+       // sign block
+       if dzk, _, err = zone.Key.Derive(label.Name, "gns"); err != nil {
                return err
        }
-       if err = zm.srv.StoreNamecache(ctx, query, blk); err != nil {
+       if err = blkNC.Sign(dzk); err != nil {
+               return err
+       }
+
+       // publish GNS block to namecache
+       if err = zm.StoreNamecache(ctx, query, blkNC); err != nil {
                return err
        }
        return nil
diff --git a/src/gnunet/transport/reader_writer.go 
b/src/gnunet/transport/reader_writer.go
index 2cee0a9..a1a7bbe 100644
--- a/src/gnunet/transport/reader_writer.go
+++ b/src/gnunet/transport/reader_writer.go
@@ -116,7 +116,10 @@ func ReadMessage(ctx context.Context, rdr io.ReadCloser, 
buf []byte) (msg messag
                err = fmt.Errorf("message{%d} is nil", mh.MsgType)
                return
        }
-       err = data.Unmarshal(msg, buf[:mh.MsgSize])
+       if err = data.Unmarshal(msg, buf[:mh.MsgSize]); err != nil {
+               return
+       }
+       err = msg.Init()
        /*
                // DEBUG: incoming messages
                if mh.MsgType == enums.MSG_DHT_P2P_RESULT {

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