gnunet-go

GNUnet Bindings for Go
Log | Files | Refs | README | LICENSE

commit db8edd2189c526a79039661844314081739010e1
parent 7bfc54a066e5a2c65f43ef17783fc9b3abbfbef0
Author: Bernd Fix <brf@hoi-polloi.org>
Date:   Mon, 17 Feb 2020 12:34:45 +0100

Started revocation implementation.

Diffstat:
Asrc/cmd/pow-test/main.go | 48++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/gnunet/message/factory.go | 12++++++++++++
Asrc/gnunet/message/msg_revocation.go | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/gnunet/service/gns/module.go | 10++++++++++
Asrc/gnunet/service/revocation/pow.go | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 482 insertions(+), 0 deletions(-)

diff --git a/src/cmd/pow-test/main.go b/src/cmd/pow-test/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "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() + fmt.Printf("Leading zeros required: %d\n", bits) + + // generate a random key pair + pkey, _ := ed25519.NewKeypair() + + // initialize RevData structure + rd := revocation.NewRevData(0, pkey) + + // pre-set difficulty + difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE) + + 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()) +} diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go @@ -81,6 +81,18 @@ func NewEmptyMessage(msgType uint16) (Message, error) { return NewNamecacheCacheMsg(nil), nil case NAMECACHE_BLOCK_CACHE_RESPONSE: return NewNamecacheCacheResponseMsg(), nil + + //------------------------------------------------------------------ + // Revocation + //------------------------------------------------------------------ + case REVOCATION_QUERY: + return NewRevocationQueryMsg(nil), nil + case REVOCATION_QUERY_RESPONSE: + return NewRevocationQueryResponseMsg(true), nil + case REVOCATION_REVOKE: + return NewRevocationRevokeMsg(0, nil, nil), nil + case REVOCATION_REVOKE_RESPONSE: + return NewRevocationRevokeResponseMsg(false), nil } return nil, errors.New(fmt.Sprintf("Unknown message type %d", msgType)) } diff --git a/src/gnunet/message/msg_revocation.go b/src/gnunet/message/msg_revocation.go @@ -0,0 +1,181 @@ +// 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 message + +import ( + "fmt" + + "gnunet/crypto" + "gnunet/enums" + "gnunet/util" + + "github.com/bfix/gospel/crypto/ed25519" +) + +//---------------------------------------------------------------------- +// REVOCATION_QUERY +//---------------------------------------------------------------------- + +// RevocationQueryMsg +type RevocationQueryMsg struct { + MsgSize uint16 `order:"big"` // total size of message + MsgType uint16 `order:"big"` // REVOCATION_QUERY (636) + Reserved uint32 `order:"big"` // Reserved for future use + Zone []byte `size:"32"` // Zone that is to be checked for revocation +} + +// NewRevocationQueryMsg creates a new message for a given zone. +func NewRevocationQueryMsg(zone *ed25519.PublicKey) *RevocationQueryMsg { + msg := &RevocationQueryMsg{ + MsgSize: 40, + MsgType: REVOCATION_QUERY, + Reserved: 0, + Zone: make([]byte, 32), + } + if zone != nil { + copy(msg.Zone, zone.Bytes()) + } + return msg +} + +// String returns a human-readable representation of the message. +func (m *RevocationQueryMsg) String() string { + return fmt.Sprintf("RevocationQueryMsg{zone=%s}", util.EncodeBinaryToString(m.Zone)) +} + +// Header returns the message header in a separate instance. +func (msg *RevocationQueryMsg) Header() *MessageHeader { + return &MessageHeader{msg.MsgSize, msg.MsgType} +} + +//---------------------------------------------------------------------- +// REVOCATION_QUERY_RESPONSE +//---------------------------------------------------------------------- + +// RevocationQueryResponseMsg +type RevocationQueryResponseMsg struct { + MsgSize uint16 `order:"big"` // total size of message + MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637) + Valid uint32 `order:"big"` // revoked(0), valid(1) +} + +// NewRevocationQueryResponseMsg creates a new response for a query. +func NewRevocationQueryResponseMsg(revoked bool) *RevocationQueryResponseMsg { + valid := 1 + if revoked { + valid = 0 + } + return &RevocationQueryResponseMsg{ + MsgSize: 8, + MsgType: REVOCATION_QUERY_RESPONSE, + Valid: uint32(valid), + } +} + +// String returns a human-readable representation of the message. +func (m *RevocationQueryResponseMsg) String() string { + return fmt.Sprintf("RevocationQueryResponseMsg{valid=%d}", m.Valid) +} + +// Header returns the message header in a separate instance. +func (msg *RevocationQueryResponseMsg) Header() *MessageHeader { + return &MessageHeader{msg.MsgSize, msg.MsgType} +} + +//---------------------------------------------------------------------- +// REVOCATION_REVOKE +//---------------------------------------------------------------------- + +// 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 +} + +// NewRevocationRevokeMsg creates a new message for a given zone. +func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig *ed25519.EcSignature) *RevocationRevokeMsg { + msg := &RevocationRevokeMsg{ + MsgSize: 120, + MsgType: REVOCATION_REVOKE, + Reserved: 0, + PoW: pow, + Signature: make([]byte, 64), + Purpose: &crypto.SignaturePurpose{ + Size: 40, + Purpose: enums.SIG_REVOCATION, + }, + ZoneKey: make([]byte, 32), + } + if zoneKey != nil { + copy(msg.ZoneKey, zoneKey.Bytes()) + } + if sig != nil { + copy(msg.Signature, sig.Bytes()) + } + return msg +} + +// 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)) +} + +// Header returns the message header in a separate instance. +func (msg *RevocationRevokeMsg) Header() *MessageHeader { + return &MessageHeader{msg.MsgSize, msg.MsgType} +} + +//---------------------------------------------------------------------- +// REVOCATION_REVOKE_RESPONSE +//---------------------------------------------------------------------- + +// 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? +} + +// NewRevocationRevokeResponseMsg creates a new response for a query. +func NewRevocationRevokeResponseMsg(success bool) *RevocationRevokeResponseMsg { + status := 0 + if success { + status = 1 + } + return &RevocationRevokeResponseMsg{ + MsgSize: 8, + MsgType: REVOCATION_QUERY_RESPONSE, + Success: uint32(status), + } +} + +// String returns a human-readable representation of the message. +func (m *RevocationRevokeResponseMsg) String() string { + return fmt.Sprintf("RevocationRevokeResponseMsg{success=%v}", m.Success == 1) +} + +// Header returns the message header in a separate instance. +func (msg *RevocationRevokeResponseMsg) Header() *MessageHeader { + return &MessageHeader{msg.MsgSize, msg.MsgType} +} diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go @@ -280,6 +280,16 @@ func (gns *GNSModule) ResolveRelative(labels []string, pkey *ed25519.PublicKey, set.AddRecord(inst.rec) } } + + // if the result set is not empty, add all supplemental records we are not + // asking for explicitly. + if set.Count > 0 { + for _, rec := range records { + if !kind.HasType(int(rec.Type)) && (int(rec.Flags)&enums.GNS_FLAG_SUPPL) != 0 { + set.AddRecord(rec) + } + } + } return } diff --git a/src/gnunet/service/revocation/pow.go b/src/gnunet/service/revocation/pow.go @@ -0,0 +1,231 @@ +// 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 ( + "bytes" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "sync" + + "gnunet/crypto" + "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" +) + +//---------------------------------------------------------------------- +// Revocation 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 + + // 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), + } + copy(rd.ZoneKey, zoneKey.Bytes()) + blob, err := data.Marshal(rd) + if err != 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 + } + return r.Nonce +} + +// Next selects the next nonce to be tested. +func (r *RevData) Next() { + var incr func(pos int) + incr = func(pos int) { + r.blob[pos]++ + if r.blob[pos] != 0 || pos == 0 { + return + } + incr(pos - 1) + } + incr(7) +} + +// Compute calculates the current result for a RevData 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, 64) + if err != nil { + return nil, err + } + + // generate keys + skey := crypto.NewSymmetricKey() + copy(skey.AESKey, k[:32]) + copy(skey.TwofishKey, k[32:]) + + // generate initialization vector + iv := crypto.NewSymmetricIV() + prk := hkdf.Extract(sha512.New, k[:32], []byte("gnunet-proof-of-work-ivAES!")) + rdr := hkdf.Expand(sha256.New, prk, []byte("gnunet-revocation-proof-of-work")) + rdr.Read(iv.AESIv) + prk = hkdf.Extract(sha512.New, k[32:], []byte("gnunet-proof-of-work-ivFISH")) + rdr = hkdf.Expand(sha256.New, prk, []byte("gnunet-revocation-proof-of-work")) + rdr.Read(iv.TwofishIv) + + // perform encryption + enc, err := crypto.SymmetricEncrypt(r.blob, skey, iv) + if err != nil { + return nil, err + } + + // compute result + result, err := scrypt.Key(enc, []byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 64) + return math.NewIntFromBytes(result), nil +} + +//---------------------------------------------------------------------- +// Command types for Worker +//---------------------------------------------------------------------- + +// 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 +} + +//---------------------------------------------------------------------- +// 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) +} + +// 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) +} + +// 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) +} + +// BreakResponse is a reply to the BreakCmd message +type BreakResponse struct { + ID int // Command identifier (to relate responses) + Nonce uint64 // last checked nonce value +} + +//---------------------------------------------------------------------- +// Worker instance +//---------------------------------------------------------------------- + +// Task represents a currently active PoW calculation +type Task struct { + ID int + rev *RevData + active bool +} + +// 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 +} + +func NewWorker() *Worker { + return &Worker{ + tasks: make(map[int]*Task), + wg: new(sync.WaitGroup), + } +} + +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, + } + w.tasks[task.ID] = task + } + + default: + // compute a single round of currently active tasks + } + } +}