gnunet-svn
[Top][All Lists]
Advanced

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

[gnunet-go] branch master updated: Milestone #3 (RC1)


From: gnunet
Subject: [gnunet-go] branch master updated: Milestone #3 (RC1)
Date: Sun, 24 May 2020 13:40:05 +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 fdf2318  Milestone #3 (RC1)
fdf2318 is described below

commit fdf23180919ec42422694f1f1801eead1ea576e8
Author: Bernd Fix <address@hidden>
AuthorDate: Sun May 24 13:34:05 2020 +0200

    Milestone #3 (RC1)
---
 src/cmd/gnunet-service-revocation-go/main.go | 105 ++++++++
 src/cmd/pow-test/main.go                     |  51 ----
 src/cmd/revoke-zonekey/main.go               | 192 +++++++++++++++
 src/gnunet/config/config.go                  |  18 +-
 src/gnunet/message/factory.go                |   2 +-
 src/gnunet/message/msg_revocation.go         |  35 ++-
 src/gnunet/modules.go                        |  19 +-
 src/gnunet/service/gns/module.go             |  21 +-
 src/gnunet/service/gns/service.go            |  59 +++++
 src/gnunet/service/revocation/module.go      | 124 ++++++++++
 src/gnunet/service/revocation/pow.go         | 346 ++++++++++++++++-----------
 src/gnunet/service/revocation/service.go     | 160 +++++++++++++
 src/gnunet/util/database.go                  |  72 ++++++
 src/gnunet/util/key_value_store.go           | 188 +++++++++++++++
 src/gnunet/util/time.go                      |  12 +
 15 files changed, 1172 insertions(+), 232 deletions(-)

diff --git a/src/cmd/gnunet-service-revocation-go/main.go 
b/src/cmd/gnunet-service-revocation-go/main.go
new file mode 100644
index 0000000..c829aea
--- /dev/null
+++ b/src/cmd/gnunet-service-revocation-go/main.go
@@ -0,0 +1,105 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+       "flag"
+       "os"
+       "os/signal"
+       "syscall"
+       "time"
+
+       "github.com/bfix/gospel/logger"
+       "gnunet/config"
+       "gnunet/service"
+       "gnunet/service/revocation"
+)
+
+func main() {
+       defer func() {
+               logger.Println(logger.INFO, "[revocation] Bye.")
+               // flush last messages
+               logger.Flush()
+       }()
+       logger.Println(logger.INFO, "[revocation] Starting service...")
+
+       var (
+               cfgFile  string
+               srvEndp  string
+               err      error
+               logLevel int
+       )
+       // handle command line arguments
+       flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet 
configuration file")
+       flag.StringVar(&srvEndp, "s", "", "REVOCATION service end-point")
+       flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level 
(default: INFO)")
+       flag.Parse()
+
+       // read configuration file and set missing arguments.
+       if err = config.ParseConfig(cfgFile); err != nil {
+               logger.Printf(logger.ERROR, "[revocation] Invalid configuration 
file: %s\n", err.Error())
+               return
+       }
+
+       // apply configuration
+       logger.SetLogLevel(logLevel)
+       if len(srvEndp) == 0 {
+               srvEndp = config.Cfg.GNS.Endpoint
+       }
+
+       // start a new REVOCATION service
+       rvc := revocation.NewRevocationService()
+       srv := service.NewServiceImpl("revocation", rvc)
+       if err = srv.Start(srvEndp); err != nil {
+               logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n", 
err.Error())
+               return
+       }
+
+       // handle OS signals
+       sigCh := make(chan os.Signal, 5)
+       signal.Notify(sigCh)
+
+       // heart beat
+       tick := time.NewTicker(5 * time.Minute)
+
+loop:
+       for {
+               select {
+               // handle OS signals
+               case sig := <-sigCh:
+                       switch sig {
+                       case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
+                               logger.Printf(logger.INFO, "[revocation] 
Terminating service (on signal '%s')\n", sig)
+                               break loop
+                       case syscall.SIGHUP:
+                               logger.Println(logger.INFO, "[revocation] 
SIGHUP")
+                       case syscall.SIGURG:
+                               // TODO: 
https://github.com/golang/go/issues/37942
+                       default:
+                               logger.Println(logger.INFO, "[revocation] 
Unhandled signal: "+sig.String())
+                       }
+               // handle heart beat
+               case now := <-tick.C:
+                       logger.Println(logger.INFO, "[revocation] Heart beat at 
"+now.String())
+               }
+       }
+
+       // terminating service
+       srv.Stop()
+}
diff --git a/src/cmd/pow-test/main.go b/src/cmd/pow-test/main.go
deleted file mode 100644
index 247f442..0000000
--- a/src/cmd/pow-test/main.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package main
-
-import (
-       "encoding/hex"
-       "flag"
-       "fmt"
-       "log"
-
-       "gnunet/service/revocation"
-
-       "github.com/bfix/gospel/crypto/ed25519"
-       "github.com/bfix/gospel/math"
-)
-
-func main() {
-       var (
-               quiet bool
-               bits  int
-       )
-       flag.IntVar(&bits, "b", 25, "Number of leading zero bits")
-       flag.BoolVar(&quiet, "q", false, "Be quiet")
-       flag.Parse()
-
-       // pre-set difficulty
-       fmt.Printf("Leading zeros required: %d\n", bits)
-       difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE)
-       fmt.Printf("==> Difficulty: %v\n", difficulty)
-
-       // generate a random key pair
-       pkey, _ := ed25519.NewKeypair()
-
-       // initialize RevData structure
-       rd := revocation.NewRevData(0, pkey)
-
-       var count uint64 = 0
-       for {
-               result, err := rd.Compute()
-               if err != nil {
-                       log.Fatal(err)
-               }
-               //fmt.Printf("Nonce=%d, Result=(%d) %v\n", rd.GetNonce(), 
result.BitLen(), result)
-               if result.Cmp(difficulty) < 0 {
-                       break
-               }
-               count++
-               rd.Next()
-       }
-       fmt.Printf("PoW found after %d iterations:\n", count)
-       fmt.Printf("--> Nonce=%d\n", rd.GetNonce())
-       fmt.Printf("    REV = %s\n", hex.EncodeToString(rd.GetBlob()))
-}
diff --git a/src/cmd/revoke-zonekey/main.go b/src/cmd/revoke-zonekey/main.go
new file mode 100644
index 0000000..2bbd90f
--- /dev/null
+++ b/src/cmd/revoke-zonekey/main.go
@@ -0,0 +1,192 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+       "context"
+       "encoding/hex"
+       "flag"
+       "log"
+       "os"
+       "os/signal"
+       "sync"
+       "syscall"
+
+       "gnunet/service/revocation"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+)
+
+func main() {
+       log.Println("*** Compute revocation data for a zone key")
+       log.Println("*** Copyright (c) 2020, Bernd Fix  >Y<")
+       log.Println("*** This is free software distributed under the Affero GPL 
v3.")
+
+       // handle command line arguments
+       var (
+               verbose  bool   // be verbose with messages
+               bits     int    // number of leading zero-bit requested
+               zonekey  string // zonekey to be revoked
+               filename string // name of file for persistance
+       )
+       flag.IntVar(&bits, "b", 25, "Number of leading zero bits")
+       flag.BoolVar(&verbose, "v", false, "verbose output")
+       flag.StringVar(&zonekey, "z", "", "Zone key to be revoked")
+       flag.StringVar(&filename, "f", "", "Name of file to store revocation")
+       flag.Parse()
+
+       // define layout of persistant data
+       var revData struct {
+               Rd      *revocation.RevData // Revocation data
+               T       util.RelativeTime   // time spend in calculations
+               Last    uint64              // last value used for PoW test
+               Numbits uint8               // number of leading zero-bits
+       }
+       dataBuf := make([]byte, 377)
+
+       // read revocation object from file
+       file, err := os.Open(filename)
+       cont := true
+       if err != nil {
+               if len(zonekey) != 52 {
+                       log.Fatal("Missing or invalid zonekey and no file 
specified -- aborting")
+               }
+               keyData, err := util.DecodeStringToBinary(zonekey, 32)
+               if err != nil {
+                       log.Fatal("Invalid zonekey: " + err.Error())
+               }
+               pkey := ed25519.NewPublicKeyFromBytes(keyData)
+               revData.Rd = revocation.NewRevData(util.AbsoluteTimeNow(), pkey)
+               revData.Numbits = uint8(bits)
+               revData.T = util.NewRelativeTime(0)
+               cont = false
+       } else {
+               n, err := file.Read(dataBuf)
+               if err != nil {
+                       log.Fatal("Error reading file: " + err.Error())
+               }
+               if n != len(dataBuf) {
+                       log.Fatal("File corrupted -- aborting")
+               }
+               if err = data.Unmarshal(&revData, dataBuf); err != nil {
+                       log.Fatal("File corrupted: " + err.Error())
+               }
+               bits = int(revData.Numbits)
+               if err = file.Close(); err != nil {
+                       log.Fatal("Error closing file: " + err.Error())
+               }
+       }
+
+       if cont {
+               log.Printf("Revocation calculation started at %s\n", 
revData.Rd.Timestamp.String())
+               log.Printf("Time spent on calculation: %s\n", 
revData.T.String())
+               log.Printf("Last tested PoW value: %d\n", revData.Last)
+               log.Println("Continuing...")
+       } else {
+               log.Println("Starting new revocation calculation...")
+       }
+       log.Println("Press ^C to abort...")
+
+       // pre-set difficulty
+       log.Printf("Difficulty: %d\n", bits)
+       if bits < 25 {
+               log.Println("WARNING: difficulty is less than 25!")
+       }
+
+       // Start or continue calculation
+       startTime := util.AbsoluteTimeNow()
+       ctx, cancelFcn := context.WithCancel(context.Background())
+       wg := new(sync.WaitGroup)
+       wg.Add(1)
+       go func() {
+               defer wg.Done()
+               if result, last := revData.Rd.Compute(ctx, bits, revData.Last); 
result != 32 {
+                       log.Printf("Incomplete revocation: Only %d of 32 PoWs 
available!\n", result)
+                       revData.Last = last
+                       revData.T = util.AbsoluteTimeNow().Diff(startTime)
+                       log.Println("Writing revocation data to file...")
+                       file, err := os.Create(filename)
+                       if err != nil {
+                               log.Fatal("Can't write to output file: " + 
err.Error())
+                       }
+                       buf, err := data.Marshal(&revData)
+                       if err != nil {
+                               log.Fatal("Internal error: " + err.Error())
+                       }
+                       if len(buf) != len(dataBuf) {
+                               log.Fatal("Internal error: Buffer mismatch")
+                       }
+                       n, err := file.Write(buf)
+                       if err != nil {
+                               log.Fatal("Can't write to output file: " + 
err.Error())
+                       }
+                       if n != len(dataBuf) {
+                               log.Fatal("Can't write data to output file!")
+                       }
+                       if err = file.Close(); err != nil {
+                               log.Fatal("Error closing file: " + err.Error())
+                       }
+               } else {
+                       log.Println("Revocation data object:")
+                       log.Println("   0x" + 
hex.EncodeToString(revData.Rd.Blob()))
+                       log.Println("Status:")
+                       rc := revData.Rd.Verify()
+                       switch {
+                       case rc == -1:
+                               log.Println("    Missing/invalid signature")
+                       case rc == -2:
+                               log.Println("    Expired revocation")
+                       case rc == -3:
+                               log.Println("    Wrong PoW sequence order")
+                       case rc < 25:
+                               log.Println("    Difficulty to small")
+                       default:
+                               log.Printf("    Difficulty: %d\n", rc)
+                       }
+               }
+       }()
+
+       go func() {
+               // handle OS signals
+               sigCh := make(chan os.Signal, 5)
+               signal.Notify(sigCh)
+       loop:
+               for {
+                       select {
+                       // handle OS signals
+                       case sig := <-sigCh:
+                               switch sig {
+                               case syscall.SIGKILL, syscall.SIGINT, 
syscall.SIGTERM:
+                                       log.Printf("Terminating (on signal 
'%s')\n", sig)
+                                       cancelFcn()
+                                       break loop
+                               case syscall.SIGHUP:
+                                       log.Println("SIGHUP")
+                               case syscall.SIGURG:
+                                       // TODO: 
https://github.com/golang/go/issues/37942
+                               default:
+                                       log.Println("Unhandled signal: " + 
sig.String())
+                               }
+                       }
+               }
+       }()
+       wg.Wait()
+}
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 01dadea..690b186 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -54,6 +54,15 @@ type NamecacheConfig struct {
        Endpoint string `json:"endpoint"` // end-point of Namecache service
 }
 
+///////////////////////////////////////////////////////////////////////
+// Revocation configuration
+
+// RevocationConfig
+type RevocationConfig struct {
+       Endpoint string `json:"endpoint"` // end-point of Revocation service
+       Storage  string `json:"storage"`  // persistance mechanism for 
revocation data
+}
+
 ///////////////////////////////////////////////////////////////////////
 
 // Environment settings
@@ -61,10 +70,11 @@ type Environ map[string]string
 
 // Config is the aggregated configuration for GNUnet.
 type Config struct {
-       Env       Environ          `json:"environ"`
-       DHT       *DHTConfig       `json:"dht"`
-       GNS       *GNSConfig       `json:"gns"`
-       Namecache *NamecacheConfig `json:"namecache"`
+       Env        Environ           `json:"environ"`
+       DHT        *DHTConfig        `json:"dht"`
+       GNS        *GNSConfig        `json:"gns"`
+       Namecache  *NamecacheConfig  `json:"namecache"`
+       Revocation *RevocationConfig `json:"revocation"`
 }
 
 var (
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 33e806b..68a24d5 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -92,7 +92,7 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
        case REVOCATION_QUERY_RESPONSE:
                return NewRevocationQueryResponseMsg(true), nil
        case REVOCATION_REVOKE:
-               return NewRevocationRevokeMsg(0, nil, nil), nil
+               return NewRevocationRevokeMsg(nil, nil), nil
        case REVOCATION_REVOKE_RESPONSE:
                return NewRevocationRevokeResponseMsg(false), nil
        }
diff --git a/src/gnunet/message/msg_revocation.go 
b/src/gnunet/message/msg_revocation.go
index fea727c..8c7db06 100644
--- a/src/gnunet/message/msg_revocation.go
+++ b/src/gnunet/message/msg_revocation.go
@@ -21,8 +21,6 @@ package message
 import (
        "fmt"
 
-       "gnunet/crypto"
-       "gnunet/enums"
        "gnunet/util"
 
        "github.com/bfix/gospel/crypto/ed25519"
@@ -104,28 +102,23 @@ func (msg *RevocationQueryResponseMsg) Header() 
*MessageHeader {
 
 // RevocationRevokeMsg
 type RevocationRevokeMsg struct {
-       MsgSize   uint16                   `order:"big"` // total size of 
message
-       MsgType   uint16                   `order:"big"` // REVOCATION_QUERY 
(636)
-       Reserved  uint32                   `order:"big"` // Reserved for future 
use
-       PoW       uint64                   `order:"big"` // Proof-of-work: 
nonce that satisfy condition
-       Signature []byte                   `size:"64"`   // Signature of the 
revocation.
-       Purpose   *crypto.SignaturePurpose // Size and purpose of signature (8 
bytes)
-       ZoneKey   []byte                   `size:"32"` // Zone key to be revoked
+       MsgSize   uint16            `order:"big"` // total size of message
+       MsgType   uint16            `order:"big"` // REVOCATION_REVOKE (638)
+       Timestamp util.AbsoluteTime // Timestamp of revocation creation
+       PoWs      []uint64          `size:"32" order:"big"` // (Sorted) list of 
PoW values
+       Signature []byte            `size:"64"`             // Signature 
(Proof-of-ownership).
+       ZoneKey   []byte            `size:"32"`             // public zone key 
to be revoked
 }
 
 // NewRevocationRevokeMsg creates a new message for a given zone.
-func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig 
*ed25519.EcSignature) *RevocationRevokeMsg {
+func NewRevocationRevokeMsg(zoneKey *ed25519.PublicKey, sig 
*ed25519.EcSignature) *RevocationRevokeMsg {
        msg := &RevocationRevokeMsg{
-               MsgSize:   120,
+               MsgSize:   364,
                MsgType:   REVOCATION_REVOKE,
-               Reserved:  0,
-               PoW:       pow,
+               Timestamp: util.AbsoluteTimeNow(),
+               PoWs:      make([]uint64, 32),
                Signature: make([]byte, 64),
-               Purpose: &crypto.SignaturePurpose{
-                       Size:    40,
-                       Purpose: enums.SIG_REVOCATION,
-               },
-               ZoneKey: make([]byte, 32),
+               ZoneKey:   make([]byte, 32),
        }
        if zoneKey != nil {
                copy(msg.ZoneKey, zoneKey.Bytes())
@@ -138,7 +131,7 @@ func NewRevocationRevokeMsg(pow uint64, zoneKey 
*ed25519.PublicKey, sig *ed25519
 
 // String returns a human-readable representation of the message.
 func (m *RevocationRevokeMsg) String() string {
-       return fmt.Sprintf("RevocationRevokeMsg{pow=%d,zone=%s}", m.PoW, 
util.EncodeBinaryToString(m.ZoneKey))
+       return fmt.Sprintf("RevocationRevokeMsg{zone=%s}", 
util.EncodeBinaryToString(m.ZoneKey))
 }
 
 // Header returns the message header in a separate instance.
@@ -153,8 +146,8 @@ func (msg *RevocationRevokeMsg) Header() *MessageHeader {
 // RevocationRevokeResponseMsg
 type RevocationRevokeResponseMsg struct {
        MsgSize uint16 `order:"big"` // total size of message
-       MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637)
-       Success uint32 `order:"big"` // Revoke successful?
+       MsgType uint16 `order:"big"` // REVOCATION_REVOKE_RESPONSE (639)
+       Success uint32 `order:"big"` // Revoke successful? (0=no, 1=yes)
 }
 
 // NewRevocationRevokeResponseMsg creates a new response for a query.
diff --git a/src/gnunet/modules.go b/src/gnunet/modules.go
index 063b914..24dc92b 100644
--- a/src/gnunet/modules.go
+++ b/src/gnunet/modules.go
@@ -32,13 +32,15 @@ import (
        "gnunet/service/dht"
        "gnunet/service/gns"
        "gnunet/service/namecache"
+       "gnunet/service/revocation"
 )
 
 // List of all GNUnet service module instances
 type Instances struct {
-       GNS       *gns.GNSModule
-       Namecache *namecache.NamecacheModule
-       DHT       *dht.DHTModule
+       GNS        *gns.GNSModule
+       Namecache  *namecache.NamecacheModule
+       DHT        *dht.DHTModule
+       Revocation *revocation.RevocationModule
 }
 
 // Local reference to instance list
@@ -55,10 +57,15 @@ func init() {
        // DHT (no calls to other modules)
        Modules.DHT = new(dht.DHTModule)
 
+       // Revocation (no calls to other modules)
+       Modules.Revocation = revocation.NewRevocationModule()
+
        // GNS (calls Namecache, DHT and Identity)
        Modules.GNS = &gns.GNSModule{
-               LookupLocal:  Modules.Namecache.Get,
-               StoreLocal:   Modules.Namecache.Put,
-               LookupRemote: Modules.DHT.Get,
+               LookupLocal:      Modules.Namecache.Get,
+               StoreLocal:       Modules.Namecache.Put,
+               LookupRemote:     Modules.DHT.Get,
+               RevocationQuery:  Modules.Revocation.Query,
+               RevocationRevoke: Modules.Revocation.Revoke,
        }
 }
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index e885d2d..5e787d5 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -27,6 +27,7 @@ import (
        "gnunet/enums"
        "gnunet/message"
        "gnunet/service"
+       "gnunet/service/revocation"
        "gnunet/util"
 
        "github.com/bfix/gospel/crypto/ed25519"
@@ -111,9 +112,11 @@ func NewQuery(pkey *ed25519.PublicKey, label string) 
*Query {
 // GNSModule handles the resolution of GNS names to RRs bundled in a block.
 type GNSModule struct {
        // Use function references for calls to methods in other modules:
-       LookupLocal  func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
-       StoreLocal   func(ctx *service.SessionContext, block *message.GNSBlock) 
error
-       LookupRemote func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
+       LookupLocal      func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
+       StoreLocal       func(ctx *service.SessionContext, block 
*message.GNSBlock) error
+       LookupRemote     func(ctx *service.SessionContext, query *Query) 
(*message.GNSBlock, error)
+       RevocationQuery  func(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error)
+       RevocationRevoke func(ctx *service.SessionContext, rd 
*revocation.RevData) (success bool, err error)
 }
 
 // Resolve a GNS name with multiple labels. If pkey is not nil, the name
@@ -158,6 +161,12 @@ func (gns *GNSModule) ResolveAbsolute(
                err = ErrUnknownTLD
                return
        }
+       // check if zone key has been revoked
+       var valid bool
+       set = message.NewGNSRecordSet()
+       if valid, err = gns.RevocationQuery(ctx, pkey); err != nil || !valid {
+               return
+       }
        // continue with resolution relative to a zone.
        return gns.ResolveRelative(ctx, labels[1:], pkey, kind, mode, depth)
 }
@@ -229,6 +238,12 @@ func (gns *GNSModule) ResolveRelative(
                        if len(labels) == 1 && 
!kind.HasType(enums.GNS_TYPE_PKEY) {
                                labels = append(labels, "@")
                        }
+                       // check if zone key has been revoked
+                       if valid, err := gns.RevocationQuery(ctx, pkey); err != 
nil || !valid {
+                               // revoked key -> no results!
+                               records = make([]*message.GNSResourceRecord, 0)
+                               break
+                       }
                } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); 
hdlr != nil {
                        // (2) GNS2DNS records
                        inst := hdlr.(*Gns2DnsHandler)
diff --git a/src/gnunet/service/gns/service.go 
b/src/gnunet/service/gns/service.go
index 464e622..f6310de 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -28,6 +28,7 @@ import (
        "gnunet/enums"
        "gnunet/message"
        "gnunet/service"
+       "gnunet/service/revocation"
        "gnunet/transport"
        "gnunet/util"
 
@@ -59,6 +60,8 @@ func NewGNSService() service.Service {
        inst.LookupLocal = inst.LookupNamecache
        inst.StoreLocal = inst.StoreNamecache
        inst.LookupRemote = inst.LookupDHT
+       inst.RevocationQuery = inst.QueryKeyRevocation
+       inst.RevocationRevoke = inst.RevokeKey
        return inst
 }
 
@@ -167,6 +170,60 @@ loop:
        ctx.Cancel()
 }
 
+//======================================================================
+
+//
+func (s *GNSService) QueryKeyRevocation(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error) {
+       logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", 
util.EncodeBinaryToString(pkey.Bytes()))
+
+       // assemble request
+       req := message.NewRevocationQueryMsg(pkey)
+
+       // get response from Revocation service
+       var resp message.Message
+       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
+               return
+       }
+
+       // handle message depending on its type
+       logger.Println(logger.DBG, "[gns] Handling response from Revocation 
service")
+       valid = false
+       switch m := resp.(type) {
+       case *message.RevocationQueryResponseMsg:
+               valid = (m.Valid == 1)
+       }
+       return
+}
+
+//
+func (s *GNSService) RevokeKey(ctx *service.SessionContext, rd 
*revocation.RevData) (success bool, err error) {
+       logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", 
util.EncodeBinaryToString(rd.ZoneKey))
+
+       // assemble request
+       req := message.NewRevocationRevokeMsg(nil, nil)
+       req.Timestamp = rd.Timestamp
+       copy(req.PoWs, rd.PoWs)
+       copy(req.Signature, rd.Signature)
+       copy(req.ZoneKey, rd.ZoneKey)
+
+       // get response from Revocation service
+       var resp message.Message
+       if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", 
config.Cfg.Revocation.Endpoint, req); err != nil {
+               return
+       }
+
+       // handle message depending on its type
+       logger.Println(logger.DBG, "[gns] Handling response from Revocation 
service")
+       success = false
+       switch m := resp.(type) {
+       case *message.RevocationRevokeResponseMsg:
+               success = (m.Success == 1)
+       }
+       return
+}
+
+//======================================================================
+
 // LookupNamecache
 func (s *GNSService) LookupNamecache(ctx *service.SessionContext, query 
*Query) (block *message.GNSBlock, err error) {
        logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", 
hex.EncodeToString(query.Key.Bits))
@@ -266,6 +323,8 @@ func (s *GNSService) StoreNamecache(ctx 
*service.SessionContext, block *message.
        return
 }
 
+//======================================================================
+
 // LookupDHT
 func (s *GNSService) LookupDHT(ctx *service.SessionContext, query *Query) 
(block *message.GNSBlock, err error) {
        logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", 
hex.EncodeToString(query.Key.Bits))
diff --git a/src/gnunet/service/revocation/module.go 
b/src/gnunet/service/revocation/module.go
new file mode 100644
index 0000000..b5c8a16
--- /dev/null
+++ b/src/gnunet/service/revocation/module.go
@@ -0,0 +1,124 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package revocation
+
+import (
+       "gnunet/config"
+       "gnunet/service"
+       "gnunet/util"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/data"
+       "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// "GNUnet Revocation" implementation
+//======================================================================
+
+// RevocationModule handles the revocation-related calls to other modules.
+type RevocationModule struct {
+       bloomf *data.BloomFilter  // bloomfilter for fast revocation check
+       kvs    util.KeyValueStore // storage for known revocations
+}
+
+// Init a revocation module
+func (m *RevocationModule) Init() error {
+       // Initialize access to revocation data storage
+       var err error
+       if m.kvs, err = util.OpenKVStore(config.Cfg.Revocation.Storage); err != 
nil {
+               return err
+       }
+       // traverse the storage and build bloomfilter for all keys
+       m.bloomf = data.NewBloomFilter(1000000, 1e-8)
+       keys, err := m.kvs.List()
+       if err != nil {
+               return err
+       }
+       for _, key := range keys {
+               buf, err := util.DecodeStringToBinary(key, 32)
+               if err != nil {
+                       return err
+               }
+               m.bloomf.Add(buf)
+       }
+       return nil
+}
+
+// NewRevocationModule returns an initialized revocation module
+func NewRevocationModule() *RevocationModule {
+       m := new(RevocationModule)
+       if err := m.Init(); err != nil {
+               logger.Printf(logger.ERROR, "[revocation] Failed to initialize 
module: %s\n", err.Error())
+               return nil
+       }
+       return m
+}
+
+// Query return true if the pkey is valid (not revoked) and false
+// if the pkey has been revoked.
+func (s *RevocationModule) Query(ctx *service.SessionContext, pkey 
*ed25519.PublicKey) (valid bool, err error) {
+       // fast check first: is the key in the bloomfilter?
+       data := pkey.Bytes()
+       if !s.bloomf.Contains(data) {
+               // no: it is valid (not revoked)
+               return true, nil
+       }
+       // check in store to detect false-positives
+       key := util.EncodeBinaryToString(data)
+       if _, err = s.kvs.Get(key); err != nil {
+               logger.Printf(logger.ERROR, "[revocation] Failed to locate key 
'%s' in store: %s\n", key, err.Error())
+               // assume not revoked...
+               return true, err
+       }
+       // key seems to be revoked
+       return false, nil
+}
+
+// Revoke
+func (s *RevocationModule) Revoke(ctx *service.SessionContext, rd *RevData) 
(success bool, err error) {
+       // verify the revocation data
+       rc := rd.Verify()
+       switch {
+       case rc == -1:
+               logger.Println(logger.WARN, "[revocation] Revoke: 
Missing/invalid signature")
+               return false, nil
+       case rc == -2:
+               logger.Println(logger.WARN, "[revocation] Revoke: Expired 
revocation")
+               return false, nil
+       case rc == -3:
+               logger.Println(logger.WARN, "[revocation] Revoke: Wrong PoW 
sequence order")
+               return false, nil
+       case rc < 25:
+               logger.Println(logger.WARN, "[revocation] Revoke: Difficulty to 
small")
+               return false, nil
+       }
+       // store the revocation data
+       // (1) add it to the bloomfilter
+       s.bloomf.Add(rd.ZoneKey)
+       // (2) add it to the store
+       var buf []byte
+       key := util.EncodeBinaryToString(rd.ZoneKey)
+       if buf, err = data.Marshal(rd); err != nil {
+               return false, err
+       }
+       value := util.EncodeBinaryToString(buf)
+       err = s.kvs.Put(key, value)
+       return true, err
+}
diff --git a/src/gnunet/service/revocation/pow.go 
b/src/gnunet/service/revocation/pow.go
index 07c6241..f4b6b9d 100644
--- a/src/gnunet/service/revocation/pow.go
+++ b/src/gnunet/service/revocation/pow.go
@@ -20,71 +20,74 @@ package revocation
 
 import (
        "bytes"
-       "crypto/cipher"
-       "crypto/sha256"
-       "crypto/sha512"
+       "context"
        "encoding/binary"
-       "sync"
+       "time"
 
+       "gnunet/crypto"
+       "gnunet/enums"
+       "gnunet/message"
        "gnunet/util"
 
        "github.com/bfix/gospel/crypto/ed25519"
        "github.com/bfix/gospel/data"
        "github.com/bfix/gospel/math"
-       "golang.org/x/crypto/hkdf"
-       "golang.org/x/crypto/scrypt"
-       "golang.org/x/crypto/twofish"
+       "golang.org/x/crypto/argon2"
 )
 
 //----------------------------------------------------------------------
-// Revocation data
+// Proof-of-Work data
 //----------------------------------------------------------------------
 
-// RevData is the revocation data structure (wire format)
-type RevData struct {
-       Nonce   uint64 `order:"big"` // start with this nonce value
-       ZoneKey []byte `size:"32"`   // public zone key to be revoked
+// PoWData is the proof-of-work data
+type PoWData struct {
+       PoW       uint64            `order:"big"` // start with this PoW value
+       Timestamp util.AbsoluteTime // Timestamp of creation
+       ZoneKey   []byte            `size:"32"` // public zone key to be revoked
 
        // transient attributes (not serialized)
        blob []byte // binary representation of serialized data
 }
 
-// NewRevData creates a RevData instance for the given arguments.
-func NewRevData(nonce uint64, zoneKey *ed25519.PublicKey) *RevData {
-       rd := &RevData{
-               Nonce:   nonce,
-               ZoneKey: make([]byte, 32),
+// NewPoWData creates a PoWData instance for the given arguments.
+func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey []byte) *PoWData {
+       rd := &PoWData{
+               PoW:       0,
+               Timestamp: ts,
+               ZoneKey:   zoneKey,
        }
-       copy(rd.ZoneKey, zoneKey.Bytes())
-       blob, err := data.Marshal(rd)
-       if err != nil {
+       if rd.SetPoW(pow) != nil {
                return nil
        }
-       rd.blob = blob
        return rd
 }
 
-// GetNonce returns the last checked nonce value
-func (r *RevData) GetNonce() uint64 {
-       if r.blob != nil {
-               var val uint64
-               binary.Read(bytes.NewReader(r.blob[:8]), binary.BigEndian, &val)
-               r.Nonce = val
+func (p *PoWData) SetPoW(pow uint64) error {
+       p.PoW = pow
+       blob, err := data.Marshal(p)
+       if err != nil {
+               return err
        }
-       return r.Nonce
+       p.blob = blob
+       return nil
 }
 
-// GetBlob returns the binary representation of RevData
-func (r *RevData) GetBlob() []byte {
-       return r.blob
+// GetPoW returns the last checked PoW value
+func (p *PoWData) GetPoW() uint64 {
+       if p.blob != nil {
+               var val uint64
+               binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian, &val)
+               p.PoW = val
+       }
+       return p.PoW
 }
 
-// Next selects the next nonce to be tested.
-func (r *RevData) Next() {
+// Next selects the next PoW to be tested.
+func (p *PoWData) Next() {
        var incr func(pos int)
        incr = func(pos int) {
-               r.blob[pos]++
-               if r.blob[pos] != 0 || pos == 0 {
+               p.blob[pos]++
+               if p.blob[pos] != 0 || pos == 0 {
                        return
                }
                incr(pos - 1)
@@ -92,140 +95,191 @@ func (r *RevData) Next() {
        incr(7)
 }
 
-// Compute calculates the current result for a RevData content.
+// Compute calculates the current result for a PoWData content.
 // The result is returned as a big integer value.
-func (r *RevData) Compute() (*math.Int, error) {
-
-       // generate key material
-       k, err := scrypt.Key(r.blob, []byte("gnunet-revocation-proof-of-work"), 
2, 8, 2, 32)
-       if err != nil {
-               return nil, err
-       }
-
-       // generate initialization vector
-       iv := make([]byte, 16)
-       prk := hkdf.Extract(sha512.New, k, []byte("gnunet-proof-of-work-iv"))
-       rdr := hkdf.Expand(sha256.New, prk, 
[]byte("gnunet-revocation-proof-of-work"))
-       rdr.Read(iv)
-
-       // Encrypt with Twofish CFB stream cipher
-       out := make([]byte, len(r.blob))
-       tf, err := twofish.NewCipher(k)
-       if err != nil {
-               return nil, err
-       }
-       cipher.NewCFBEncrypter(tf, iv).XORKeyStream(out, r.blob)
-
-       // compute result
-       result, err := scrypt.Key(out, 
[]byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 64)
-       return math.NewIntFromBytes(result), nil
+func (p *PoWData) Compute() *math.Int {
+       key := argon2.Key(p.blob, []byte("gnunet-revocation-proof-of-work"), 3, 
1024, 1, 64)
+       return math.NewIntFromBytes(key)
 }
 
 //----------------------------------------------------------------------
-// Command types for Worker
+// Revocation data
 //----------------------------------------------------------------------
 
-// StartCmd starts the PoW calculation beginng at given nonce. If a
-// revocation is initiated the first time, the nonce is 0. If the computation
-// was interrupted (because the revocation service was shutting down), the
-// computation can resume for the next unchecked nonce value.
-// see: StartResponse
-type StartCmd struct {
-       ID   int      // Command identifier (to relate responses)
-       task *RevData // RevData instance to be started
-}
-
-// PauseCmd temporarily pauses the calculation of a PoW.
-// see: PauseResponse
-type PauseCmd struct {
-       ID     int // Command identifier (to relate responses)
-       taskID int // identifier for PoW task
-}
-
-// ResumeCmd resumes a paused PoW calculation.
-// see: ResumeResponse
-type ResumeCmd struct {
-       ID     int // Command identifier (to relate responses)
-       taskID int // identifier for PoW task
-}
-
-// BreakCmd interrupts a running PoW calculation
-type BreakCmd struct {
-       ID     int // Command identifier (to relate responses)
-       taskID int // identifier for PoW task
+// RevData is the revocation data (wire format)
+type RevData struct {
+       Timestamp util.AbsoluteTime // Timestamp of creation
+       PoWs      []uint64          `size:"32" order:"big"` // (Sorted) list of 
PoW values
+       Signature []byte            `size:"64"`             // Signature 
(Proof-of-ownership).
+       ZoneKey   []byte            `size:"32"`             // public zone key 
to be revoked
 }
 
-//----------------------------------------------------------------------
-// Response types for Worker
-//----------------------------------------------------------------------
-
-// StartResponse is a reply to the StartCmd message
-type StartResponse struct {
-       ID     int   // Command identifier (to relate responses)
-       taskID int   // identifier for PoW task
-       err    error // error code (nil on success)
+// SignedRevData is the block of data signed for a RevData instance.
+type SignedRevData struct {
+       Purpose   *crypto.SignaturePurpose
+       ZoneKey   []byte            `size:"32"` // public zone key to be revoked
+       Timestamp util.AbsoluteTime // Timestamp of creation
 }
 
-// PauseResponse is a reply to the PauseCmd message
-type PauseResponse struct {
-       ID  int   // Command identifier (to relate responses)
-       err error // error code (nil on success)
+// NewRevData initializes a new RevData instance
+func NewRevData(ts util.AbsoluteTime, pkey *ed25519.PublicKey) *RevData {
+       rd := &RevData{
+               Timestamp: ts,
+               PoWs:      make([]uint64, 32),
+               Signature: make([]byte, 64),
+               ZoneKey:   make([]byte, 32),
+       }
+       copy(rd.ZoneKey, pkey.Bytes())
+       return rd
 }
 
-// ResumeResponse is a reply to the ResumeCmd message
-type ResumeResponse struct {
-       ID  int   // Command identifier (to relate responses)
-       err error // error code (nil on success)
+// NewRevDataFromMsg initializes a new RevData instance from a GNUnet message
+func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData {
+       rd := &RevData{
+               Timestamp: m.Timestamp,
+               Signature: util.Clone(m.Signature),
+               ZoneKey:   util.Clone(m.ZoneKey),
+       }
+       for i, pow := range m.PoWs {
+               rd.PoWs[i] = pow
+       }
+       return rd
 }
 
-// BreakResponse is a reply to the BreakCmd message
-type BreakResponse struct {
-       ID    int    // Command identifier (to relate responses)
-       Nonce uint64 // last checked nonce value
+// Sign the revocation data
+func (rd *RevData) Sign(skey *ed25519.PrivateKey) error {
+       sigBlock := &SignedRevData{
+               Purpose: &crypto.SignaturePurpose{
+                       Size:    48,
+                       Purpose: enums.SIG_REVOCATION,
+               },
+               ZoneKey:   rd.ZoneKey,
+               Timestamp: rd.Timestamp,
+       }
+       sigData, err := data.Marshal(sigBlock)
+       if err != nil {
+               return err
+       }
+       sig, err := skey.EcSign(sigData)
+       if err != nil {
+               return err
+       }
+       copy(rd.Signature, sig.Bytes())
+       return nil
 }
 
-//----------------------------------------------------------------------
-// Worker instance
-//----------------------------------------------------------------------
-
-// Task represents a currently active PoW calculation
-type Task struct {
-       ID     int
-       rev    *RevData
-       active bool
-}
+// Verify a revocation object: returns the (smallest) number of leading
+// zero-bits in the PoWs of this revocation; a number > 0, but smaller
+// than the minimum (25) indicates invalid PoWs; a value of -1 indicates
+// a failed signature; -2 indicates an expired revocation and -3 for a
+// "out-of-order" PoW sequence.
+func (rd *RevData) Verify() int {
+
+       // (1) check signature
+       sigBlock := &SignedRevData{
+               Purpose: &crypto.SignaturePurpose{
+                       Size:    48,
+                       Purpose: enums.SIG_REVOCATION,
+               },
+               ZoneKey:   rd.ZoneKey,
+               Timestamp: rd.Timestamp,
+       }
+       sigData, err := data.Marshal(sigBlock)
+       if err != nil {
+               return -1
+       }
+       pkey := ed25519.NewPublicKeyFromBytes(rd.ZoneKey)
+       sig, err := ed25519.NewEcSignatureFromBytes(rd.Signature)
+       if err != nil {
+               return -1
+       }
+       valid, err := pkey.EcVerify(sigData, sig)
+       if err != nil || !valid {
+               return -1
+       }
 
-// Worker is the revocation worker. It is responsible to manage ad schedule
-// the proof-of-work tasks for revocations.
-type Worker struct {
-       tasks map[int]*Task
-       wg    *sync.WaitGroup
-}
+       // (2) check PoWs
+       var (
+               zbits int    = 512
+               last  uint64 = 0
+       )
+       for _, pow := range rd.PoWs {
+               // check sequence order
+               if pow <= last {
+                       return -3
+               }
+               last = pow
+               // compute number of leading zero-bits
+               work := NewPoWData(pow, rd.Timestamp, rd.ZoneKey)
+               lzb := 512 - work.Compute().BitLen()
+               if lzb < zbits {
+                       zbits = lzb
+               }
+       }
 
-func NewWorker() *Worker {
-       return &Worker{
-               tasks: make(map[int]*Task),
-               wg:    new(sync.WaitGroup),
+       // (3) check expiration
+       ttl := time.Duration((zbits-24)*365*24) * time.Hour
+       if util.AbsoluteTimeNow().Add(ttl).Expired() {
+               return -2
        }
+       return zbits
 }
 
-func (w *Worker) Run(wg *sync.WaitGroup, cmdCh chan interface{}, responseCh 
chan interface{}) {
-       defer wg.Done()
-       for {
-               select {
-               case cmd := <-cmdCh:
-                       switch x := cmd.(type) {
-                       case *StartCmd:
-                               task := &Task{
-                                       ID:     util.NextID(),
-                                       rev:    x.task,
-                                       active: true,
+// Compute tries to compute a valid Revocation; it returns the number of
+// solved PoWs. The computation is complete if 32 PoWs have been found.
+func (rd *RevData) Compute(ctx context.Context, bits int, last uint64) (int, 
uint64) {
+       // set difficulty based on requested number of leading zero-bits
+       difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE)
+
+       // initialize a new work record (single PoW computation)
+       work := NewPoWData(0, rd.Timestamp, rd.ZoneKey)
+
+       // work on all PoWs in a revocation data structure; make sure all PoWs
+       // are set to a valid value (that results in a valid compute() result
+       // below a given threshold)
+       for i, pow := range rd.PoWs {
+               // handle "new" pow value: set it to last_pow+1
+               // this ensures a correctly sorted pow list by design.
+               if pow == 0 {
+                       pow = last
+               }
+               if pow == 0 && i > 0 {
+                       pow = rd.PoWs[i-1] + 1
+               }
+               // prepare for PoW_i
+               work.SetPoW(pow)
+
+               // Find PoW value in an (interruptable) loop
+               out := make(chan bool)
+               go func() {
+                       for {
+                               res := work.Compute()
+                               if res.Cmp(difficulty) < 0 {
+                                       break
                                }
-                               w.tasks[task.ID] = task
+                               work.Next()
+                       }
+                       out <- true
+               }()
+       loop:
+               for {
+                       select {
+                       case <-out:
+                               rd.PoWs[i] = work.GetPoW()
+                               break loop
+                       case <-ctx.Done():
+                               return i, work.GetPoW() + 1
                        }
-
-               default:
-                       // compute a single round of currently active tasks
                }
        }
+       // we have found all valid PoW values.
+       return 32, 0
+}
+
+func (rd *RevData) Blob() []byte {
+       blob, err := data.Marshal(rd)
+       if err != nil {
+               return nil
+       }
+       return blob
 }
diff --git a/src/gnunet/service/revocation/service.go 
b/src/gnunet/service/revocation/service.go
new file mode 100644
index 0000000..f2a6ec3
--- /dev/null
+++ b/src/gnunet/service/revocation/service.go
@@ -0,0 +1,160 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package revocation
+
+import (
+       "io"
+
+       "gnunet/message"
+       "gnunet/service"
+       "gnunet/transport"
+
+       "github.com/bfix/gospel/crypto/ed25519"
+       "github.com/bfix/gospel/logger"
+)
+
+//----------------------------------------------------------------------
+// "GNUnet Revocation" service implementation
+//----------------------------------------------------------------------
+
+// RevocationService
+type RevocationService struct {
+       RevocationModule
+}
+
+// NewRevocationService
+func NewRevocationService() service.Service {
+       // instantiate service and assemble a new Revocation handler.
+       inst := new(RevocationService)
+       return inst
+}
+
+// Start the Revocation service
+func (s *RevocationService) Start(spec string) error {
+       return nil
+}
+
+// Stop the Revocation service
+func (s *RevocationService) Stop() error {
+       return nil
+}
+
+// Serve a client channel.
+func (s *RevocationService) ServeClient(ctx *service.SessionContext, mc 
*transport.MsgChannel) {
+
+       reqId := 0
+loop:
+       for {
+               // receive next message from client
+               reqId++
+               logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for 
client request...\n", ctx.Id, reqId)
+               msg, err := mc.Receive(ctx.Signaller())
+               if err != nil {
+                       if err == io.EOF {
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Client channel closed.\n", ctx.Id, reqId)
+                       } else if err == transport.ErrChannelInterrupted {
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Service operation interrupted.\n", ctx.Id, reqId)
+                       } else {
+                               logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Message-receive failed: %s\n", ctx.Id, reqId, err.Error())
+                       }
+                       break loop
+               }
+               logger.Printf(logger.INFO, "[revocation:%d:%d] Received 
request: %v\n", ctx.Id, reqId, msg)
+
+               // handle request
+               switch m := msg.(type) {
+               case *message.RevocationQueryMsg:
+                       
//----------------------------------------------------------
+                       // REVOCATION_QUERY
+                       
//----------------------------------------------------------
+                       go func(id int, m *message.RevocationQueryMsg) {
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Query request received.\n", ctx.Id, id)
+                               var resp *message.RevocationQueryResponseMsg
+                               ctx.Add()
+                               defer func() {
+                                       // send response
+                                       if resp != nil {
+                                               if err := mc.Send(resp, 
ctx.Signaller()); err != nil {
+                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.Id, id, err.Error())
+                                               }
+                                       }
+                                       // go-routine finished
+                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Query request finished.\n", ctx.Id, id)
+                                       ctx.Remove()
+                               }()
+
+                               pkey := ed25519.NewPublicKeyFromBytes(m.Zone)
+                               valid, err := s.Query(ctx, pkey)
+                               if err != nil {
+                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.Id, id, 
err.Error())
+                                       if err == 
transport.ErrChannelInterrupted {
+                                               resp = nil
+                                       }
+                                       return
+                               }
+                               resp = 
message.NewRevocationQueryResponseMsg(valid)
+                       }(reqId, m)
+
+               case *message.RevocationRevokeMsg:
+                       
//----------------------------------------------------------
+                       // REVOCATION_REVOKE
+                       
//----------------------------------------------------------
+                       go func(id int, m *message.RevocationRevokeMsg) {
+                               logger.Printf(logger.INFO, "[revocation:%d:%d] 
Revoke request received.\n", ctx.Id, id)
+                               var resp *message.RevocationRevokeResponseMsg
+                               ctx.Add()
+                               defer func() {
+                                       // send response
+                                       if resp != nil {
+                                               if err := mc.Send(resp, 
ctx.Signaller()); err != nil {
+                                                       
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", 
ctx.Id, id, err.Error())
+                                               }
+                                       }
+                                       // go-routine finished
+                                       logger.Printf(logger.DBG, 
"[revocation:%d:%d] Revoke request finished.\n", ctx.Id, id)
+                                       ctx.Remove()
+                               }()
+
+                               rd := NewRevDataFromMsg(m)
+                               valid, err := s.Revoke(ctx, rd)
+                               if err != nil {
+                                       logger.Printf(logger.ERROR, 
"[revocation:%d:%d] Failed to revoke key: %s\n", ctx.Id, id, err.Error())
+                                       if err == 
transport.ErrChannelInterrupted {
+                                               resp = nil
+                                       }
+                                       return
+                               }
+                               resp = 
message.NewRevocationRevokeResponseMsg(valid)
+                       }(reqId, m)
+
+               default:
+                       
//----------------------------------------------------------
+                       // UNKNOWN message type received
+                       
//----------------------------------------------------------
+                       logger.Printf(logger.ERROR, "[revocation:%d:%d] 
Unhandled message of type (%d)\n", ctx.Id, reqId, msg.Header().MsgType)
+                       break loop
+               }
+       }
+       // close client connection
+       mc.Close()
+
+       // cancel all tasks running for this session/connection
+       logger.Printf(logger.INFO, "[revocation:%d] Start closing session... 
[%d]\n", ctx.Id, ctx.Waiting())
+       ctx.Cancel()
+}
diff --git a/src/gnunet/util/database.go b/src/gnunet/util/database.go
new file mode 100644
index 0000000..48da749
--- /dev/null
+++ b/src/gnunet/util/database.go
@@ -0,0 +1,72 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package util
+
+import (
+       "database/sql"
+       "fmt"
+       "os"
+       "strings"
+
+       _ "github.com/go-sql-driver/mysql"
+       _ "github.com/mattn/go-sqlite3"
+)
+
+// Error messages related to databases
+var (
+       ErrSqlInvalidDatabaseSpec = fmt.Errorf("Invalid database specification")
+       ErrSqlNoDatabase          = fmt.Errorf("Database not found")
+)
+
+// ConnectSqlDatabase connects to an SQL database (various types and flavors):
+// The 'spec' option defines the arguments required to connect to a database;
+// the meaning and format of the arguments depends on the specific SQL 
database.
+// The arguments are seperated by the '+' character; the first (and mandatory)
+// argument defines the SQL database type. Other arguments depend on the value
+// of this first argument.
+// The following SQL types are implemented:
+// * 'sqlite3': SQLite3-compatible database; the second argument specifies the
+//              file that holds the data (e.g. "sqlite3+/home/user/store.db")
+// * 'mysql':   A MySQL-compatible database; the second argument specifies the
+//              information required to log into the database (e.g.
+//              "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]").
+func ConnectSqlDatabase(spec string) (db *sql.DB, err error) {
+       // split spec string into segments
+       specs := strings.Split(spec, ":")
+       if len(specs) < 2 {
+               return nil, ErrSqlInvalidDatabaseSpec
+       }
+       switch specs[0] {
+       case "sqlite3":
+               // check if the database file exists
+               var fi os.FileInfo
+               if fi, err = os.Stat(specs[1]); err != nil {
+                       return nil, ErrSqlNoDatabase
+               }
+               if fi.IsDir() {
+                       return nil, ErrSqlNoDatabase
+               }
+               // open the database file
+               return sql.Open("sqlite3", specs[1])
+       case "mysql":
+               // just connect to the database
+               return sql.Open("mysql", specs[1])
+       }
+       return nil, ErrSqlInvalidDatabaseSpec
+}
diff --git a/src/gnunet/util/key_value_store.go 
b/src/gnunet/util/key_value_store.go
new file mode 100644
index 0000000..d87b747
--- /dev/null
+++ b/src/gnunet/util/key_value_store.go
@@ -0,0 +1,188 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix  >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package util
+
+import (
+       "context"
+       "database/sql"
+       "fmt"
+       "strconv"
+       "strings"
+
+       "github.com/go-redis/redis"
+)
+
+// Error messages related to the key/value-store implementations
+var (
+       ErrKVSInvalidSpec  = fmt.Errorf("Invalid KVStore specification")
+       ErrKVSNotAvailable = fmt.Errorf("KVStore not available")
+)
+
+// KeyValueStore interface for implementations that store and retrieve
+// key/value pairs. Keys and values are strings.
+type KeyValueStore interface {
+       Put(key string, value string) error // put a key/value pair into store
+       Get(key string) (string, error)     // retrieve a value for a key from 
store
+       List() ([]string, error)            // get all keys from the store
+}
+
+// OpenKVStore opens a key/value store for further put/get operations.
+// The 'spec' option specifies the arguments required to connect to a specific
+// persistence mechanism. The arguments in the 'spec' string are separated by
+// the '+' character.
+// The first argument specifies the type of key/value store to be used; the
+// meaning and format of the following arguments depend on this type.
+//
+// Key/Value Store types defined:
+// * 'redis':   Use a Redis server for persistance; the specification is
+//              "redis+addr+[passwd]+db". 'db' must be an integer value.
+// * 'mysql':   MySQL-compatible database (see 'database.go' for details)
+// * 'sqlite3': SQLite3-compatible database (see 'database.go' for details)
+func OpenKVStore(spec string) (KeyValueStore, error) {
+       // check specification string
+       specs := strings.Split(spec, "+")
+       if len(specs) < 2 {
+               return nil, ErrKVSInvalidSpec
+       }
+       switch specs[0] {
+       case "redis":
+               //--------------------------------------------------------------
+               // NoSQL-based persistance
+               //--------------------------------------------------------------
+               if len(specs) < 4 {
+                       return nil, ErrKVSInvalidSpec
+               }
+               db, err := strconv.Atoi(specs[3])
+               if err != nil {
+                       return nil, ErrKVSInvalidSpec
+               }
+               kvs := new(KvsRedis)
+               kvs.db = db
+               kvs.client = redis.NewClient(&redis.Options{
+                       Addr:     specs[1],
+                       Password: specs[2],
+                       DB:       db,
+               })
+               if kvs.client == nil {
+                       err = ErrKVSNotAvailable
+               }
+               return kvs, err
+
+       case "sqlite3", "mysql":
+               //--------------------------------------------------------------
+               // SQL-based persistance
+               //--------------------------------------------------------------
+               kvs := new(KvsSql)
+               var err error
+
+               // connect to SQL database
+               kvs.db, err = ConnectSqlDatabase(spec)
+               if err != nil {
+                       return nil, err
+               }
+               // get number of key/value pairs (as a check for existing table)
+               row := kvs.db.QueryRow("select count(*) from store")
+               var num int
+               if row.Scan(&num) != nil {
+                       return nil, ErrKVSNotAvailable
+               }
+               return kvs, nil
+       }
+       return nil, ErrKVSInvalidSpec
+}
+
+//======================================================================
+// NoSQL-based key-value-stores
+//======================================================================
+
+// Redis-based key/value store
+type KvsRedis struct {
+       client *redis.Client // client connection
+       db     int           // index to database
+}
+
+// Put a key/value pair into the store
+func (kvs *KvsRedis) Put(key string, value string) error {
+       return kvs.client.Set(context.TODO(), key, value, 0).Err()
+}
+
+// Get a value for a given key from store
+func (kvs *KvsRedis) Get(key string) (value string, err error) {
+       return kvs.client.Get(context.TODO(), key).Result()
+}
+
+// Get a list of all keys in store
+func (kvs *KvsRedis) List() (keys []string, err error) {
+       var (
+               crs  uint64
+               segm []string
+               ctx  = context.TODO()
+       )
+       for {
+               segm, crs, err = kvs.client.Scan(ctx, crs, "*", 10).Result()
+               if err != nil {
+                       return nil, err
+               }
+               if crs == 0 {
+                       break
+               }
+               keys = append(keys, segm...)
+       }
+       return
+}
+
+//======================================================================
+// SQL-based key-value-store
+//======================================================================
+
+// SQL-based key/value store
+type KvsSql struct {
+       db *sql.DB
+}
+
+// Put a key/value pair into the store
+func (kvs *KvsSql) Put(key string, value string) error {
+       _, err := kvs.db.Exec("insert into store(key,value) values(?,?)", key, 
value)
+       return err
+}
+
+// Get a value for a given key from store
+func (kvs *KvsSql) Get(key string) (value string, err error) {
+       row := kvs.db.QueryRow("select value from store where key=?", key)
+       err = row.Scan(&value)
+       return
+}
+
+// Get a list of all keys in store
+func (kvs *KvsSql) List() (keys []string, err error) {
+       var (
+               rows *sql.Rows
+               key  string
+       )
+       rows, err = kvs.db.Query("select key from store")
+       if err == nil {
+               for rows.Next() {
+                       if err = rows.Scan(&key); err != nil {
+                               break
+                       }
+                       keys = append(keys, key)
+               }
+       }
+       return
+}
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index e1e0e30..bf2a1c2 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -69,6 +69,18 @@ func (t AbsoluteTime) Add(d time.Duration) AbsoluteTime {
        }
 }
 
+// Diff returns the relative time between two absolute times;
+// the ordering of the absolute times doesn't matter.
+func (t AbsoluteTime) Diff(t2 AbsoluteTime) RelativeTime {
+       var d uint64
+       if t.Compare(t2) == 1 {
+               d = t.Val - t2.Val
+       } else {
+               d = t2.Val - t.Val
+       }
+       return RelativeTime{d}
+}
+
 // Expired returns true if the timestamp is in the past.
 func (t AbsoluteTime) Expired() bool {
        // check for "never"

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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