Use status-im whisper/whisperv6 module everywhere
This commit is contained in:
parent
f3880f8fe1
commit
5b551c67fa
|
@ -820,6 +820,14 @@
|
|||
pruneopts = "NUT"
|
||||
revision = "55370cdfd9f288059b03c04e23784bb8829a894c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a2075e4e314cff4aadaf3ffe18b77659d259b1a46be9d2bdb0d5b9f8222f420c"
|
||||
name = "github.com/status-im/whisper"
|
||||
packages = ["whisperv6"]
|
||||
pruneopts = "NUT"
|
||||
revision = "f41584950703450b0370e85560ba8d9322794ead"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:572c783a763db6383aca3179976eb80e4c900f52eba56cba8bb2e3cea7ce720e"
|
||||
name = "github.com/stretchr/testify"
|
||||
|
@ -1106,6 +1114,7 @@
|
|||
"github.com/status-im/migrate/source/go_bindata",
|
||||
"github.com/status-im/rendezvous",
|
||||
"github.com/status-im/rendezvous/server",
|
||||
"github.com/status-im/whisper/whisperv6",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/stretchr/testify/suite",
|
||||
|
|
|
@ -161,3 +161,7 @@
|
|||
[[constraint]]
|
||||
name = "github.com/status-im/migrate"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/status-im/whisper"
|
||||
|
|
|
@ -17,13 +17,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/logutils"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
"github.com/status-im/status-go/t/helpers"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
|
|
|
@ -29,9 +29,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/params"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
|
|
|
@ -3,7 +3,7 @@ package mailserver
|
|||
import (
|
||||
"testing"
|
||||
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
|
|
|
@ -30,8 +30,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/params"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
17
node/node.go
17
node/node.go
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/mailserver"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/peer"
|
||||
|
@ -29,6 +28,7 @@ import (
|
|||
"github.com/status-im/status-go/services/status"
|
||||
"github.com/status-im/status-go/static"
|
||||
"github.com/status-im/status-go/timesource"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
|
@ -271,17 +271,18 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
|
|||
whisperServiceConfig := &whisper.Config{
|
||||
MaxMessageSize: whisper.DefaultMaxMessageSize,
|
||||
MinimumAcceptedPOW: 0.001,
|
||||
TimeSource: time.Now,
|
||||
}
|
||||
|
||||
if config.WhisperConfig.EnableNTPSync {
|
||||
if whisperServiceConfig.TimeSource, err = whisperTimeSource(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
whisperService := whisper.New(whisperServiceConfig)
|
||||
|
||||
if config.WhisperConfig.EnableNTPSync {
|
||||
timesource, err := whisperTimeSource(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
whisperService.SetTimeSource(timesource)
|
||||
}
|
||||
|
||||
// enable mail service
|
||||
if config.WhisperConfig.EnableMailServer {
|
||||
if err := registerMailServer(whisperService, &config.WhisperConfig); err != nil {
|
||||
|
|
|
@ -3,7 +3,7 @@ package node
|
|||
import (
|
||||
"testing"
|
||||
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
|
||||
"github.com/status-im/status-go/db"
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"github.com/status-im/status-go/discovery"
|
||||
"github.com/status-im/status-go/params"
|
||||
|
|
|
@ -13,9 +13,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
lcrypto "github.com/libp2p/go-libp2p-crypto"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -2,7 +2,7 @@ package chat
|
|||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
var discoveryTopic = "contact-discovery"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPublicMessageToWhisper(t *testing.T) {
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/services"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/db"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ package dedup
|
|||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package dedup
|
|||
import (
|
||||
"crypto/rand"
|
||||
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
func generateMessages(count int) []*whisper.Message {
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
"github.com/status-im/status-go/services/shhext/dedup"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/t/helpers"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -2,14 +2,13 @@ package benchmarks
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -53,7 +52,6 @@ func createWhisperService() *whisper.Whisper {
|
|||
whisperServiceConfig := &whisper.Config{
|
||||
MaxMessageSize: whisper.DefaultMaxMessageSize,
|
||||
MinimumAcceptedPOW: 0.005,
|
||||
TimeSource: func() time.Time { return time.Now().UTC() },
|
||||
}
|
||||
return whisper.New(whisperServiceConfig)
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/les"
|
||||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/params"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"github.com/status-im/status-go/t/e2e"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/params"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package e2e
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/node"
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/t/utils"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/t/helpers"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/account"
|
||||
e2e "github.com/status-im/status-go/t/e2e"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,603 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// List of errors
|
||||
var (
|
||||
ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key")
|
||||
ErrInvalidSymmetricKey = errors.New("invalid symmetric key")
|
||||
ErrInvalidPublicKey = errors.New("invalid public key")
|
||||
ErrInvalidSigningPubKey = errors.New("invalid signing public key")
|
||||
ErrTooLowPoW = errors.New("message rejected, PoW too low")
|
||||
ErrNoTopics = errors.New("missing topic(s)")
|
||||
)
|
||||
|
||||
// PublicWhisperAPI provides the whisper RPC service that can be
|
||||
// use publicly without security implications.
|
||||
type PublicWhisperAPI struct {
|
||||
w *Whisper
|
||||
|
||||
mu sync.Mutex
|
||||
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
|
||||
}
|
||||
|
||||
// NewPublicWhisperAPI create a new RPC whisper service.
|
||||
func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
|
||||
api := &PublicWhisperAPI{
|
||||
w: w,
|
||||
lastUsed: make(map[string]time.Time),
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Version returns the Whisper sub-protocol version.
|
||||
func (api *PublicWhisperAPI) Version(ctx context.Context) string {
|
||||
return ProtocolVersionStr
|
||||
}
|
||||
|
||||
// Info contains diagnostic information.
|
||||
type Info struct {
|
||||
Memory int `json:"memory"` // Memory size of the floating messages in bytes.
|
||||
Messages int `json:"messages"` // Number of floating messages.
|
||||
MinPow float64 `json:"minPow"` // Minimal accepted PoW
|
||||
MaxMessageSize uint32 `json:"maxMessageSize"` // Maximum accepted message size
|
||||
}
|
||||
|
||||
// Info returns diagnostic information about the whisper node.
|
||||
func (api *PublicWhisperAPI) Info(ctx context.Context) Info {
|
||||
stats := api.w.Stats()
|
||||
return Info{
|
||||
Memory: stats.memoryUsed,
|
||||
Messages: len(api.w.messageQueue) + len(api.w.p2pMsgQueue),
|
||||
MinPow: api.w.MinPow(),
|
||||
MaxMessageSize: api.w.MaxMessageSize(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxMessageSize sets the maximum message size that is accepted.
|
||||
// Upper limit is defined by MaxMessageSize.
|
||||
func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) {
|
||||
return true, api.w.SetMaxMessageSize(size)
|
||||
}
|
||||
|
||||
// SetMinPoW sets the minimum PoW, and notifies the peers.
|
||||
func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
|
||||
return true, api.w.SetMinimumPoW(pow)
|
||||
}
|
||||
|
||||
// SetBloomFilter sets the new value of bloom filter, and notifies the peers.
|
||||
func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) {
|
||||
return true, api.w.SetBloomFilter(bloom)
|
||||
}
|
||||
|
||||
// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages.
|
||||
// Note: This function is not adding new nodes, the node needs to exists as a peer.
|
||||
func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) {
|
||||
n, err := discover.ParseNode(enode)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, api.w.AllowP2PMessagesFromPeer(n.ID[:])
|
||||
}
|
||||
|
||||
// NewKeyPair generates a new public and private key pair for message decryption and encryption.
|
||||
// It returns an ID that can be used to refer to the keypair.
|
||||
func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) {
|
||||
return api.w.NewKeyPair()
|
||||
}
|
||||
|
||||
// AddPrivateKey imports the given private key.
|
||||
func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
|
||||
key, err := crypto.ToECDSA(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return api.w.AddKeyPair(key)
|
||||
}
|
||||
|
||||
// DeleteKeyPair removes the key with the given key if it exists.
|
||||
func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
|
||||
if ok := api.w.DeleteKeyPair(key); ok {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("key pair %s not found", key)
|
||||
}
|
||||
|
||||
// HasKeyPair returns an indication if the node has a key pair that is associated with the given id.
|
||||
func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool {
|
||||
return api.w.HasKeyPair(id)
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key associated with the given key. The key is the hex
|
||||
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
|
||||
func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) {
|
||||
key, err := api.w.GetPrivateKey(id)
|
||||
if err != nil {
|
||||
return hexutil.Bytes{}, err
|
||||
}
|
||||
return crypto.FromECDSAPub(&key.PublicKey), nil
|
||||
}
|
||||
|
||||
// GetPrivateKey returns the private key associated with the given key. The key is the hex
|
||||
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
|
||||
func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) {
|
||||
key, err := api.w.GetPrivateKey(id)
|
||||
if err != nil {
|
||||
return hexutil.Bytes{}, err
|
||||
}
|
||||
return crypto.FromECDSA(key), nil
|
||||
}
|
||||
|
||||
// NewSymKey generate a random symmetric key.
|
||||
// It returns an ID that can be used to refer to the key.
|
||||
// Can be used encrypting and decrypting messages where the key is known to both parties.
|
||||
func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) {
|
||||
return api.w.GenerateSymKey()
|
||||
}
|
||||
|
||||
// AddSymKey import a symmetric key.
|
||||
// It returns an ID that can be used to refer to the key.
|
||||
// Can be used encrypting and decrypting messages where the key is known to both parties.
|
||||
func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) {
|
||||
return api.w.AddSymKeyDirect([]byte(key))
|
||||
}
|
||||
|
||||
// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID.
|
||||
func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
|
||||
return api.w.AddSymKeyFromPassword(passwd)
|
||||
}
|
||||
|
||||
// HasSymKey returns an indication if the node has a symmetric key associated with the given key.
|
||||
func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool {
|
||||
return api.w.HasSymKey(id)
|
||||
}
|
||||
|
||||
// GetSymKey returns the symmetric key associated with the given id.
|
||||
func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) {
|
||||
return api.w.GetSymKey(id)
|
||||
}
|
||||
|
||||
// DeleteSymKey deletes the symmetric key that is associated with the given id.
|
||||
func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool {
|
||||
return api.w.DeleteSymKey(id)
|
||||
}
|
||||
|
||||
// MakeLightClient turns the node into light client, which does not forward
|
||||
// any incoming messages, and sends only messages originated in this node.
|
||||
func (api *PublicWhisperAPI) MakeLightClient(ctx context.Context) bool {
|
||||
api.w.SetLightClientMode(true)
|
||||
return api.w.LightClientMode()
|
||||
}
|
||||
|
||||
// CancelLightClient cancels light client mode.
|
||||
func (api *PublicWhisperAPI) CancelLightClient(ctx context.Context) bool {
|
||||
api.w.SetLightClientMode(false)
|
||||
return !api.w.LightClientMode()
|
||||
}
|
||||
|
||||
//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go
|
||||
|
||||
// NewMessage represents a new whisper message that is posted through the RPC.
|
||||
type NewMessage struct {
|
||||
SymKeyID string `json:"symKeyID"`
|
||||
PublicKey []byte `json:"pubKey"`
|
||||
Sig string `json:"sig"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Topic TopicType `json:"topic"`
|
||||
Payload []byte `json:"payload"`
|
||||
Padding []byte `json:"padding"`
|
||||
PowTime uint32 `json:"powTime"`
|
||||
PowTarget float64 `json:"powTarget"`
|
||||
TargetPeer string `json:"targetPeer"`
|
||||
}
|
||||
|
||||
type newMessageOverride struct {
|
||||
PublicKey hexutil.Bytes
|
||||
Payload hexutil.Bytes
|
||||
Padding hexutil.Bytes
|
||||
}
|
||||
|
||||
// Post posts a message on the Whisper network.
|
||||
// returns the hash of the message in case of success.
|
||||
func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) {
|
||||
var (
|
||||
symKeyGiven = len(req.SymKeyID) > 0
|
||||
pubKeyGiven = len(req.PublicKey) > 0
|
||||
err error
|
||||
)
|
||||
|
||||
// user must specify either a symmetric or an asymmetric key
|
||||
if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
|
||||
return nil, ErrSymAsym
|
||||
}
|
||||
|
||||
params := &MessageParams{
|
||||
TTL: req.TTL,
|
||||
Payload: req.Payload,
|
||||
Padding: req.Padding,
|
||||
WorkTime: req.PowTime,
|
||||
PoW: req.PowTarget,
|
||||
Topic: req.Topic,
|
||||
}
|
||||
|
||||
// Set key that is used to sign the message
|
||||
if len(req.Sig) > 0 {
|
||||
if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Set symmetric key that is used to encrypt the message
|
||||
if symKeyGiven {
|
||||
if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption
|
||||
return nil, ErrNoTopics
|
||||
}
|
||||
if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !validateDataIntegrity(params.KeySym, aesKeyLength) {
|
||||
return nil, ErrInvalidSymmetricKey
|
||||
}
|
||||
}
|
||||
|
||||
// Set asymmetric key that is used to encrypt the message
|
||||
if pubKeyGiven {
|
||||
if params.Dst, err = crypto.UnmarshalPubkey(req.PublicKey); err != nil {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt and sent message
|
||||
whisperMsg, err := NewSentMessage(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []byte
|
||||
env, err := whisperMsg.Wrap(params, api.w.GetCurrentTime())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// send to specific node (skip PoW check)
|
||||
if len(req.TargetPeer) > 0 {
|
||||
n, err := discover.ParseNode(req.TargetPeer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse target peer: %s", err)
|
||||
}
|
||||
err = api.w.SendP2PMessage(n.ID[:], env)
|
||||
if err == nil {
|
||||
hash := env.Hash()
|
||||
result = hash[:]
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ensure that the message PoW meets the node's minimum accepted PoW
|
||||
if req.PowTarget < api.w.MinPow() {
|
||||
return nil, ErrTooLowPoW
|
||||
}
|
||||
|
||||
err = api.w.Send(env)
|
||||
if err == nil {
|
||||
hash := env.Hash()
|
||||
result = hash[:]
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// UninstallFilter is alias for Unsubscribe
|
||||
func (api *PublicWhisperAPI) UninstallFilter(id string) {
|
||||
api.w.Unsubscribe(id)
|
||||
}
|
||||
|
||||
// Unsubscribe disables and removes an existing filter.
|
||||
func (api *PublicWhisperAPI) Unsubscribe(id string) {
|
||||
api.w.Unsubscribe(id)
|
||||
}
|
||||
|
||||
//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go
|
||||
|
||||
// Criteria holds various filter options for inbound messages.
|
||||
type Criteria struct {
|
||||
SymKeyID string `json:"symKeyID"`
|
||||
PrivateKeyID string `json:"privateKeyID"`
|
||||
Sig []byte `json:"sig"`
|
||||
MinPow float64 `json:"minPow"`
|
||||
Topics []TopicType `json:"topics"`
|
||||
AllowP2P bool `json:"allowP2P"`
|
||||
}
|
||||
|
||||
type criteriaOverride struct {
|
||||
Sig hexutil.Bytes
|
||||
}
|
||||
|
||||
// Messages set up a subscription that fires events when messages arrive that match
|
||||
// the given set of criteria.
|
||||
func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) {
|
||||
var (
|
||||
symKeyGiven = len(crit.SymKeyID) > 0
|
||||
pubKeyGiven = len(crit.PrivateKeyID) > 0
|
||||
err error
|
||||
)
|
||||
|
||||
// ensure that the RPC connection supports subscriptions
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return nil, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
// user must specify either a symmetric or an asymmetric key
|
||||
if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
|
||||
return nil, ErrSymAsym
|
||||
}
|
||||
|
||||
filter := Filter{
|
||||
PoW: crit.MinPow,
|
||||
Messages: make(map[common.Hash]*ReceivedMessage),
|
||||
AllowP2P: crit.AllowP2P,
|
||||
}
|
||||
|
||||
if len(crit.Sig) > 0 {
|
||||
if filter.Src, err = crypto.UnmarshalPubkey(crit.Sig); err != nil {
|
||||
return nil, ErrInvalidSigningPubKey
|
||||
}
|
||||
}
|
||||
|
||||
for i, bt := range crit.Topics {
|
||||
if len(bt) == 0 || len(bt) > 4 {
|
||||
return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt))
|
||||
}
|
||||
filter.Topics = append(filter.Topics, bt[:])
|
||||
}
|
||||
|
||||
// listen for message that are encrypted with the given symmetric key
|
||||
if symKeyGiven {
|
||||
if len(filter.Topics) == 0 {
|
||||
return nil, ErrNoTopics
|
||||
}
|
||||
key, err := api.w.GetSymKey(crit.SymKeyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !validateDataIntegrity(key, aesKeyLength) {
|
||||
return nil, ErrInvalidSymmetricKey
|
||||
}
|
||||
filter.KeySym = key
|
||||
filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
|
||||
}
|
||||
|
||||
// listen for messages that are encrypted with the given public key
|
||||
if pubKeyGiven {
|
||||
filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID)
|
||||
if err != nil || filter.KeyAsym == nil {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
}
|
||||
|
||||
id, err := api.w.Subscribe(&filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create subscription and start waiting for message events
|
||||
rpcSub := notifier.CreateSubscription()
|
||||
go func() {
|
||||
// for now poll internally, refactor whisper internal for channel support
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if filter := api.w.GetFilter(id); filter != nil {
|
||||
for _, rpcMessage := range toMessage(filter.Retrieve()) {
|
||||
if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
|
||||
log.Error("Failed to send notification", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-rpcSub.Err():
|
||||
api.w.Unsubscribe(id)
|
||||
return
|
||||
case <-notifier.Closed():
|
||||
api.w.Unsubscribe(id)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return rpcSub, nil
|
||||
}
|
||||
|
||||
//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go
|
||||
|
||||
// Message is the RPC representation of a whisper message.
|
||||
type Message struct {
|
||||
Sig []byte `json:"sig,omitempty"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Timestamp uint32 `json:"timestamp"`
|
||||
Topic TopicType `json:"topic"`
|
||||
Payload []byte `json:"payload"`
|
||||
Padding []byte `json:"padding"`
|
||||
PoW float64 `json:"pow"`
|
||||
Hash []byte `json:"hash"`
|
||||
Dst []byte `json:"recipientPublicKey,omitempty"`
|
||||
}
|
||||
|
||||
type messageOverride struct {
|
||||
Sig hexutil.Bytes
|
||||
Payload hexutil.Bytes
|
||||
Padding hexutil.Bytes
|
||||
Hash hexutil.Bytes
|
||||
Dst hexutil.Bytes
|
||||
}
|
||||
|
||||
// ToWhisperMessage converts an internal message into an API version.
|
||||
func ToWhisperMessage(message *ReceivedMessage) *Message {
|
||||
msg := Message{
|
||||
Payload: message.Payload,
|
||||
Padding: message.Padding,
|
||||
Timestamp: message.Sent,
|
||||
TTL: message.TTL,
|
||||
PoW: message.PoW,
|
||||
Hash: message.EnvelopeHash.Bytes(),
|
||||
Topic: message.Topic,
|
||||
}
|
||||
|
||||
if message.Dst != nil {
|
||||
b := crypto.FromECDSAPub(message.Dst)
|
||||
if b != nil {
|
||||
msg.Dst = b
|
||||
}
|
||||
}
|
||||
|
||||
if isMessageSigned(message.Raw[0]) {
|
||||
b := crypto.FromECDSAPub(message.SigToPubKey())
|
||||
if b != nil {
|
||||
msg.Sig = b
|
||||
}
|
||||
}
|
||||
|
||||
return &msg
|
||||
}
|
||||
|
||||
// toMessage converts a set of messages to its RPC representation.
|
||||
func toMessage(messages []*ReceivedMessage) []*Message {
|
||||
msgs := make([]*Message, len(messages))
|
||||
for i, msg := range messages {
|
||||
msgs[i] = ToWhisperMessage(msg)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// GetFilterMessages returns the messages that match the filter criteria and
|
||||
// are received between the last poll and now.
|
||||
func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) {
|
||||
api.mu.Lock()
|
||||
f := api.w.GetFilter(id)
|
||||
if f == nil {
|
||||
api.mu.Unlock()
|
||||
return nil, fmt.Errorf("filter not found")
|
||||
}
|
||||
api.lastUsed[id] = time.Now()
|
||||
api.mu.Unlock()
|
||||
|
||||
receivedMessages := f.Retrieve()
|
||||
messages := make([]*Message, 0, len(receivedMessages))
|
||||
for _, msg := range receivedMessages {
|
||||
messages = append(messages, ToWhisperMessage(msg))
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// DeleteMessageFilter deletes a filter.
|
||||
func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) {
|
||||
api.mu.Lock()
|
||||
defer api.mu.Unlock()
|
||||
|
||||
delete(api.lastUsed, id)
|
||||
return true, api.w.Unsubscribe(id)
|
||||
}
|
||||
|
||||
// NewMessageFilter creates a new filter that can be used to poll for
|
||||
// (new) messages that satisfy the given criteria.
|
||||
func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) {
|
||||
var (
|
||||
src *ecdsa.PublicKey
|
||||
keySym []byte
|
||||
keyAsym *ecdsa.PrivateKey
|
||||
topics [][]byte
|
||||
|
||||
symKeyGiven = len(req.SymKeyID) > 0
|
||||
asymKeyGiven = len(req.PrivateKeyID) > 0
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
// user must specify either a symmetric or an asymmetric key
|
||||
if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) {
|
||||
return "", ErrSymAsym
|
||||
}
|
||||
|
||||
if len(req.Sig) > 0 {
|
||||
if src, err = crypto.UnmarshalPubkey(req.Sig); err != nil {
|
||||
return "", ErrInvalidSigningPubKey
|
||||
}
|
||||
}
|
||||
|
||||
if symKeyGiven {
|
||||
if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !validateDataIntegrity(keySym, aesKeyLength) {
|
||||
return "", ErrInvalidSymmetricKey
|
||||
}
|
||||
}
|
||||
|
||||
if asymKeyGiven {
|
||||
if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.Topics) > 0 {
|
||||
topics = make([][]byte, len(req.Topics))
|
||||
for i, topic := range req.Topics {
|
||||
topics[i] = make([]byte, TopicLength)
|
||||
copy(topics[i], topic[:])
|
||||
}
|
||||
}
|
||||
|
||||
f := &Filter{
|
||||
Src: src,
|
||||
KeySym: keySym,
|
||||
KeyAsym: keyAsym,
|
||||
PoW: req.MinPow,
|
||||
AllowP2P: req.AllowP2P,
|
||||
Topics: topics,
|
||||
Messages: make(map[common.Hash]*ReceivedMessage),
|
||||
}
|
||||
|
||||
id, err := api.w.Subscribe(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
api.mu.Lock()
|
||||
api.lastUsed[id] = time.Now()
|
||||
api.mu.Unlock()
|
||||
|
||||
return id, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package whisperv6
|
||||
|
||||
// Config represents the configuration state of a whisper node.
|
||||
type Config struct {
|
||||
MaxMessageSize uint32 `toml:",omitempty"`
|
||||
MinimumAcceptedPOW float64 `toml:",omitempty"`
|
||||
RestrictConnectionBetweenLightClients bool `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// DefaultConfig represents (shocker!) the default configuration.
|
||||
var DefaultConfig = Config{
|
||||
MaxMessageSize: DefaultMaxMessageSize,
|
||||
MinimumAcceptedPOW: DefaultMinimumPoW,
|
||||
RestrictConnectionBetweenLightClients: true,
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
Package whisper implements the Whisper protocol (version 6).
|
||||
|
||||
Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
|
||||
As such it may be likened and compared to both, not dissimilar to the
|
||||
matter/energy duality (apologies to physicists for the blatant abuse of a
|
||||
fundamental and beautiful natural principle).
|
||||
|
||||
Whisper is a pure identity-based messaging system. Whisper provides a low-level
|
||||
(non-application-specific) but easily-accessible API without being based upon
|
||||
or prejudiced by the low-level hardware attributes and characteristics,
|
||||
particularly the notion of singular endpoints.
|
||||
*/
|
||||
|
||||
// Contains the Whisper protocol constant definitions
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Whisper protocol parameters
|
||||
const (
|
||||
ProtocolVersion = uint64(6) // Protocol version number
|
||||
ProtocolVersionStr = "6.0" // The same, as a string
|
||||
ProtocolName = "shh" // Nickname of the protocol in geth
|
||||
|
||||
// whisper protocol message codes, according to EIP-627
|
||||
statusCode = 0 // used by whisper protocol
|
||||
messagesCode = 1 // normal whisper message
|
||||
powRequirementCode = 2 // PoW requirement
|
||||
bloomFilterExCode = 3 // bloom filter exchange
|
||||
p2pRequestCompleteCode = 125 // peer-to-peer message, used by Dapp protocol
|
||||
p2pRequestCode = 126 // peer-to-peer message, used by Dapp protocol
|
||||
p2pMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
|
||||
NumberOfMessageCodes = 128
|
||||
|
||||
SizeMask = byte(3) // mask used to extract the size of payload size field from the flags
|
||||
signatureFlag = byte(4)
|
||||
|
||||
TopicLength = 4 // in bytes
|
||||
signatureLength = 65 // in bytes
|
||||
aesKeyLength = 32 // in bytes
|
||||
aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize()
|
||||
keyIDSize = 32 // in bytes
|
||||
BloomFilterSize = 64 // in bytes
|
||||
flagsLength = 1
|
||||
|
||||
EnvelopeHeaderLength = 20
|
||||
|
||||
MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
|
||||
DefaultMaxMessageSize = uint32(1024 * 1024)
|
||||
DefaultMinimumPoW = 0.2
|
||||
|
||||
padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol
|
||||
messageQueueLimit = 1024
|
||||
|
||||
expirationCycle = time.Second
|
||||
transmissionCycle = 300 * time.Millisecond
|
||||
|
||||
DefaultTTL = 50 // seconds
|
||||
DefaultSyncAllowance = 10 // seconds
|
||||
)
|
||||
|
||||
// MailServer represents a mail server, capable of
|
||||
// archiving the old messages for subsequent delivery
|
||||
// to the peers. Any implementation must ensure that both
|
||||
// functions are thread-safe. Also, they must return ASAP.
|
||||
// DeliverMail should use directMessagesCode for delivery,
|
||||
// in order to bypass the expiry checks.
|
||||
type MailServer interface {
|
||||
Archive(env *Envelope)
|
||||
DeliverMail(whisperPeer *Peer, request *Envelope)
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the Whisper protocol Envelope element.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
gmath "math"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Envelope represents a clear-text data packet to transmit through the Whisper
|
||||
// network. Its contents may or may not be encrypted and signed.
|
||||
type Envelope struct {
|
||||
Expiry uint32
|
||||
TTL uint32
|
||||
Topic TopicType
|
||||
Data []byte
|
||||
Nonce uint64
|
||||
|
||||
pow float64 // Message-specific PoW as described in the Whisper specification.
|
||||
|
||||
// the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom()
|
||||
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
|
||||
bloom []byte
|
||||
}
|
||||
|
||||
// size returns the size of envelope as it is sent (i.e. public fields only)
|
||||
func (e *Envelope) size() int {
|
||||
return EnvelopeHeaderLength + len(e.Data)
|
||||
}
|
||||
|
||||
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
|
||||
func (e *Envelope) rlpWithoutNonce() []byte {
|
||||
res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data})
|
||||
return res
|
||||
}
|
||||
|
||||
// NewEnvelope wraps a Whisper message with expiration and destination data
|
||||
// included into an envelope for network forwarding.
|
||||
func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage, now time.Time) *Envelope {
|
||||
env := Envelope{
|
||||
Expiry: uint32(now.Add(time.Second * time.Duration(ttl)).Unix()),
|
||||
TTL: ttl,
|
||||
Topic: topic,
|
||||
Data: msg.Raw,
|
||||
Nonce: 0,
|
||||
}
|
||||
|
||||
return &env
|
||||
}
|
||||
|
||||
// Seal closes the envelope by spending the requested amount of time as a proof
|
||||
// of work on hashing the data.
|
||||
func (e *Envelope) Seal(options *MessageParams) error {
|
||||
if options.PoW == 0 {
|
||||
// PoW is not required
|
||||
return nil
|
||||
}
|
||||
|
||||
var target, bestBit int
|
||||
if options.PoW < 0 {
|
||||
// target is not set - the function should run for a period
|
||||
// of time specified in WorkTime param. Since we can predict
|
||||
// the execution time, we can also adjust Expiry.
|
||||
e.Expiry += options.WorkTime
|
||||
} else {
|
||||
target = e.powToFirstBit(options.PoW)
|
||||
}
|
||||
|
||||
buf := make([]byte, 64)
|
||||
h := crypto.Keccak256(e.rlpWithoutNonce())
|
||||
copy(buf[:32], h)
|
||||
|
||||
finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
|
||||
for nonce := uint64(0); time.Now().UnixNano() < finish; {
|
||||
for i := 0; i < 1024; i++ {
|
||||
binary.BigEndian.PutUint64(buf[56:], nonce)
|
||||
d := new(big.Int).SetBytes(crypto.Keccak256(buf))
|
||||
firstBit := math.FirstBitSet(d)
|
||||
if firstBit > bestBit {
|
||||
e.Nonce, bestBit = nonce, firstBit
|
||||
if target > 0 && bestBit >= target {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
nonce++
|
||||
}
|
||||
}
|
||||
|
||||
if target > 0 && bestBit < target {
|
||||
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PoW computes (if necessary) and returns the proof of work target
|
||||
// of the envelope.
|
||||
func (e *Envelope) PoW() float64 {
|
||||
if e.pow == 0 {
|
||||
e.calculatePoW(0)
|
||||
}
|
||||
return e.pow
|
||||
}
|
||||
|
||||
func (e *Envelope) calculatePoW(diff uint32) {
|
||||
buf := make([]byte, 64)
|
||||
h := crypto.Keccak256(e.rlpWithoutNonce())
|
||||
copy(buf[:32], h)
|
||||
binary.BigEndian.PutUint64(buf[56:], e.Nonce)
|
||||
d := new(big.Int).SetBytes(crypto.Keccak256(buf))
|
||||
firstBit := math.FirstBitSet(d)
|
||||
x := gmath.Pow(2, float64(firstBit))
|
||||
x /= float64(e.size())
|
||||
x /= float64(e.TTL + diff)
|
||||
e.pow = x
|
||||
}
|
||||
|
||||
func (e *Envelope) powToFirstBit(pow float64) int {
|
||||
x := pow
|
||||
x *= float64(e.size())
|
||||
x *= float64(e.TTL)
|
||||
bits := gmath.Log2(x)
|
||||
bits = gmath.Ceil(bits)
|
||||
res := int(bits)
|
||||
if res < 1 {
|
||||
res = 1
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
|
||||
func (e *Envelope) Hash() common.Hash {
|
||||
if (e.hash == common.Hash{}) {
|
||||
encoded, _ := rlp.EncodeToBytes(e)
|
||||
e.hash = crypto.Keccak256Hash(encoded)
|
||||
}
|
||||
return e.hash
|
||||
}
|
||||
|
||||
// DecodeRLP decodes an Envelope from an RLP data stream.
|
||||
func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
|
||||
raw, err := s.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The decoding of Envelope uses the struct fields but also needs
|
||||
// to compute the hash of the whole RLP-encoded envelope. This
|
||||
// type has the same structure as Envelope but is not an
|
||||
// rlp.Decoder (does not implement DecodeRLP function).
|
||||
// Only public members will be encoded.
|
||||
type rlpenv Envelope
|
||||
if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.hash = crypto.Keccak256Hash(raw)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
|
||||
func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
|
||||
message := &ReceivedMessage{Raw: e.Data}
|
||||
err := message.decryptAsymmetric(key)
|
||||
switch err {
|
||||
case nil:
|
||||
return message, nil
|
||||
case ecies.ErrInvalidPublicKey: // addressed to somebody else
|
||||
return nil, err
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
|
||||
func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
|
||||
msg = &ReceivedMessage{Raw: e.Data}
|
||||
err = msg.decryptSymmetric(key)
|
||||
if err != nil {
|
||||
msg = nil
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// Open tries to decrypt an envelope, and populates the message fields in case of success.
|
||||
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
|
||||
if watcher == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The API interface forbids filters doing both symmetric and asymmetric encryption.
|
||||
if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if watcher.expectsAsymmetricEncryption() {
|
||||
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
|
||||
if msg != nil {
|
||||
msg.Dst = &watcher.KeyAsym.PublicKey
|
||||
}
|
||||
} else if watcher.expectsSymmetricEncryption() {
|
||||
msg, _ = e.OpenSymmetric(watcher.KeySym)
|
||||
if msg != nil {
|
||||
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
||||
}
|
||||
}
|
||||
|
||||
if msg != nil {
|
||||
ok := msg.ValidateAndParse()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
msg.Topic = e.Topic
|
||||
msg.PoW = e.PoW()
|
||||
msg.TTL = e.TTL
|
||||
msg.Sent = e.Expiry - e.TTL
|
||||
msg.EnvelopeHash = e.Hash()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most).
|
||||
func (e *Envelope) Bloom() []byte {
|
||||
if e.bloom == nil {
|
||||
e.bloom = TopicToBloom(e.Topic)
|
||||
}
|
||||
return e.bloom
|
||||
}
|
||||
|
||||
// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
|
||||
func TopicToBloom(topic TopicType) []byte {
|
||||
b := make([]byte, BloomFilterSize)
|
||||
var index [3]int
|
||||
for j := 0; j < 3; j++ {
|
||||
index[j] = int(topic[j])
|
||||
if (topic[3] & (1 << uint(j))) != 0 {
|
||||
index[j] += 256
|
||||
}
|
||||
}
|
||||
|
||||
for j := 0; j < 3; j++ {
|
||||
byteIndex := index[j] / 8
|
||||
bitIndex := index[j] % 8
|
||||
b[byteIndex] = (1 << uint(bitIndex))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// GetEnvelope retrieves an envelope from the message queue by its hash.
|
||||
// It returns nil if the envelope can not be found.
|
||||
func (w *Whisper) GetEnvelope(hash common.Hash) *Envelope {
|
||||
w.poolMu.RLock()
|
||||
defer w.poolMu.RUnlock()
|
||||
return w.envelopes[hash]
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package whisperv6
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
)
|
||||
|
||||
// EventType used to define known envelope events.
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
// EventEnvelopeSent fires when envelope was sent to a peer.
|
||||
EventEnvelopeSent EventType = "envelope.sent"
|
||||
// EventEnvelopeExpired fires when envelop expired
|
||||
EventEnvelopeExpired EventType = "envelope.expired"
|
||||
// EventEnvelopeAvailable fires when envelop is available for filters
|
||||
EventEnvelopeAvailable EventType = "envelope.available"
|
||||
// EventMailServerRequestCompleted fires after mailserver sends all the requested messages
|
||||
EventMailServerRequestCompleted EventType = "mailserver.request.completed"
|
||||
// EventMailServerRequestExpired fires after mailserver the request TTL ends
|
||||
EventMailServerRequestExpired EventType = "mailserver.request.expired"
|
||||
// EventMailServerEnvelopeArchived fires after an envelope has been archived
|
||||
EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived"
|
||||
)
|
||||
|
||||
// EnvelopeEvent used for envelopes events.
|
||||
type EnvelopeEvent struct {
|
||||
Event EventType
|
||||
Hash common.Hash
|
||||
Peer discover.NodeID
|
||||
Data interface{}
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Filter represents a Whisper message filter
|
||||
type Filter struct {
|
||||
Src *ecdsa.PublicKey // Sender of the message
|
||||
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
|
||||
KeySym []byte // Key associated with the Topic
|
||||
Topics [][]byte // Topics to filter messages with
|
||||
PoW float64 // Proof of work as described in the Whisper spec
|
||||
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
|
||||
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
|
||||
id string // unique identifier
|
||||
|
||||
Messages map[common.Hash]*ReceivedMessage
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// Filters represents a collection of filters
|
||||
type Filters struct {
|
||||
watchers map[string]*Filter
|
||||
|
||||
topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic
|
||||
allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is
|
||||
|
||||
whisper *Whisper
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewFilters returns a newly created filter collection
|
||||
func NewFilters(w *Whisper) *Filters {
|
||||
return &Filters{
|
||||
watchers: make(map[string]*Filter),
|
||||
topicMatcher: make(map[TopicType]map[*Filter]struct{}),
|
||||
allTopicsMatcher: make(map[*Filter]struct{}),
|
||||
whisper: w,
|
||||
}
|
||||
}
|
||||
|
||||
// Install will add a new filter to the filter collection
|
||||
func (fs *Filters) Install(watcher *Filter) (string, error) {
|
||||
if watcher.KeySym != nil && watcher.KeyAsym != nil {
|
||||
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
|
||||
}
|
||||
|
||||
if watcher.Messages == nil {
|
||||
watcher.Messages = make(map[common.Hash]*ReceivedMessage)
|
||||
}
|
||||
|
||||
id, err := GenerateRandomID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
if fs.watchers[id] != nil {
|
||||
return "", fmt.Errorf("failed to generate unique ID")
|
||||
}
|
||||
|
||||
if watcher.expectsSymmetricEncryption() {
|
||||
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
||||
}
|
||||
|
||||
watcher.id = id
|
||||
fs.watchers[id] = watcher
|
||||
fs.addTopicMatcher(watcher)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Uninstall will remove a filter whose id has been specified from
|
||||
// the filter collection
|
||||
func (fs *Filters) Uninstall(id string) bool {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
if fs.watchers[id] != nil {
|
||||
fs.removeFromTopicMatchers(fs.watchers[id])
|
||||
delete(fs.watchers, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addTopicMatcher adds a filter to the topic matchers.
|
||||
// If the filter's Topics array is empty, it will be tried on every topic.
|
||||
// Otherwise, it will be tried on the topics specified.
|
||||
func (fs *Filters) addTopicMatcher(watcher *Filter) {
|
||||
if len(watcher.Topics) == 0 {
|
||||
fs.allTopicsMatcher[watcher] = struct{}{}
|
||||
} else {
|
||||
for _, t := range watcher.Topics {
|
||||
topic := BytesToTopic(t)
|
||||
if fs.topicMatcher[topic] == nil {
|
||||
fs.topicMatcher[topic] = make(map[*Filter]struct{})
|
||||
}
|
||||
fs.topicMatcher[topic][watcher] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeFromTopicMatchers removes a filter from the topic matchers
|
||||
func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
|
||||
delete(fs.allTopicsMatcher, watcher)
|
||||
for _, topic := range watcher.Topics {
|
||||
delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
|
||||
}
|
||||
}
|
||||
|
||||
// getWatchersByTopic returns a slice containing the filters that
|
||||
// match a specific topic
|
||||
func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter {
|
||||
res := make([]*Filter, 0, len(fs.allTopicsMatcher))
|
||||
for watcher := range fs.allTopicsMatcher {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
for watcher := range fs.topicMatcher[topic] {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Get returns a filter from the collection with a specific ID
|
||||
func (fs *Filters) Get(id string) *Filter {
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
return fs.watchers[id]
|
||||
}
|
||||
|
||||
// NotifyWatchers notifies any filter that has declared interest
|
||||
// for the envelope's topic.
|
||||
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
|
||||
var msg *ReceivedMessage
|
||||
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
|
||||
candidates := fs.getWatchersByTopic(env.Topic)
|
||||
for _, watcher := range candidates {
|
||||
if p2pMessage && !watcher.AllowP2P {
|
||||
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id))
|
||||
continue
|
||||
}
|
||||
|
||||
var match bool
|
||||
if msg != nil {
|
||||
match = watcher.MatchMessage(msg)
|
||||
} else {
|
||||
match = watcher.MatchEnvelope(env)
|
||||
if match {
|
||||
msg = env.Open(watcher)
|
||||
if msg == nil {
|
||||
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
} else {
|
||||
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
}
|
||||
|
||||
if match && msg != nil {
|
||||
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
|
||||
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
|
||||
watcher.Trigger(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filter) expectsAsymmetricEncryption() bool {
|
||||
return f.KeyAsym != nil
|
||||
}
|
||||
|
||||
func (f *Filter) expectsSymmetricEncryption() bool {
|
||||
return f.KeySym != nil
|
||||
}
|
||||
|
||||
// Trigger adds a yet-unknown message to the filter's list of
|
||||
// received messages.
|
||||
func (f *Filter) Trigger(msg *ReceivedMessage) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
|
||||
f.Messages[msg.EnvelopeHash] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve will return the list of all received messages associated
|
||||
// to a filter.
|
||||
func (f *Filter) Retrieve() (all []*ReceivedMessage) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
all = make([]*ReceivedMessage, 0, len(f.Messages))
|
||||
for _, msg := range f.Messages {
|
||||
all = append(all, msg)
|
||||
}
|
||||
|
||||
f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
|
||||
return all
|
||||
}
|
||||
|
||||
// MatchMessage checks if the filter matches an already decrypted
|
||||
// message (i.e. a Message that has already been handled by
|
||||
// MatchEnvelope when checked by a previous filter).
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
|
||||
if f.PoW > 0 && msg.PoW < f.PoW {
|
||||
return false
|
||||
}
|
||||
|
||||
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
|
||||
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
|
||||
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
|
||||
return f.SymKeyHash == msg.SymKeyHash
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchEnvelope checks if it's worth decrypting the message. If
|
||||
// it returns `true`, client code is expected to attempt decrypting
|
||||
// the message and subsequently call MatchMessage.
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
|
||||
return f.PoW <= 0 || envelope.pow >= f.PoW
|
||||
}
|
||||
|
||||
// IsPubKeyEqual checks that two public keys are equal
|
||||
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
|
||||
if !ValidatePublicKey(a) {
|
||||
return false
|
||||
} else if !ValidatePublicKey(b) {
|
||||
return false
|
||||
}
|
||||
// the curve is always the same, just compare the points
|
||||
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
|
||||
}
|
66
vendor/github.com/status-im/whisper/whisperv6/gen_criteria_json.go
generated
vendored
Normal file
66
vendor/github.com/status-im/whisper/whisperv6/gen_criteria_json.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*criteriaOverride)(nil)
|
||||
|
||||
// MarshalJSON marshals type Criteria to a json string
|
||||
func (c Criteria) MarshalJSON() ([]byte, error) {
|
||||
type Criteria struct {
|
||||
SymKeyID string `json:"symKeyID"`
|
||||
PrivateKeyID string `json:"privateKeyID"`
|
||||
Sig hexutil.Bytes `json:"sig"`
|
||||
MinPow float64 `json:"minPow"`
|
||||
Topics []TopicType `json:"topics"`
|
||||
AllowP2P bool `json:"allowP2P"`
|
||||
}
|
||||
var enc Criteria
|
||||
enc.SymKeyID = c.SymKeyID
|
||||
enc.PrivateKeyID = c.PrivateKeyID
|
||||
enc.Sig = c.Sig
|
||||
enc.MinPow = c.MinPow
|
||||
enc.Topics = c.Topics
|
||||
enc.AllowP2P = c.AllowP2P
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals type Criteria to a json string
|
||||
func (c *Criteria) UnmarshalJSON(input []byte) error {
|
||||
type Criteria struct {
|
||||
SymKeyID *string `json:"symKeyID"`
|
||||
PrivateKeyID *string `json:"privateKeyID"`
|
||||
Sig *hexutil.Bytes `json:"sig"`
|
||||
MinPow *float64 `json:"minPow"`
|
||||
Topics []TopicType `json:"topics"`
|
||||
AllowP2P *bool `json:"allowP2P"`
|
||||
}
|
||||
var dec Criteria
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.SymKeyID != nil {
|
||||
c.SymKeyID = *dec.SymKeyID
|
||||
}
|
||||
if dec.PrivateKeyID != nil {
|
||||
c.PrivateKeyID = *dec.PrivateKeyID
|
||||
}
|
||||
if dec.Sig != nil {
|
||||
c.Sig = *dec.Sig
|
||||
}
|
||||
if dec.MinPow != nil {
|
||||
c.MinPow = *dec.MinPow
|
||||
}
|
||||
if dec.Topics != nil {
|
||||
c.Topics = dec.Topics
|
||||
}
|
||||
if dec.AllowP2P != nil {
|
||||
c.AllowP2P = *dec.AllowP2P
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*messageOverride)(nil)
|
||||
|
||||
// MarshalJSON marshals type Message to a json string
|
||||
func (m Message) MarshalJSON() ([]byte, error) {
|
||||
type Message struct {
|
||||
Sig hexutil.Bytes `json:"sig,omitempty"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Timestamp uint32 `json:"timestamp"`
|
||||
Topic TopicType `json:"topic"`
|
||||
Payload hexutil.Bytes `json:"payload"`
|
||||
Padding hexutil.Bytes `json:"padding"`
|
||||
PoW float64 `json:"pow"`
|
||||
Hash hexutil.Bytes `json:"hash"`
|
||||
Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"`
|
||||
}
|
||||
var enc Message
|
||||
enc.Sig = m.Sig
|
||||
enc.TTL = m.TTL
|
||||
enc.Timestamp = m.Timestamp
|
||||
enc.Topic = m.Topic
|
||||
enc.Payload = m.Payload
|
||||
enc.Padding = m.Padding
|
||||
enc.PoW = m.PoW
|
||||
enc.Hash = m.Hash
|
||||
enc.Dst = m.Dst
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals type Message to a json string
|
||||
func (m *Message) UnmarshalJSON(input []byte) error {
|
||||
type Message struct {
|
||||
Sig *hexutil.Bytes `json:"sig,omitempty"`
|
||||
TTL *uint32 `json:"ttl"`
|
||||
Timestamp *uint32 `json:"timestamp"`
|
||||
Topic *TopicType `json:"topic"`
|
||||
Payload *hexutil.Bytes `json:"payload"`
|
||||
Padding *hexutil.Bytes `json:"padding"`
|
||||
PoW *float64 `json:"pow"`
|
||||
Hash *hexutil.Bytes `json:"hash"`
|
||||
Dst *hexutil.Bytes `json:"recipientPublicKey,omitempty"`
|
||||
}
|
||||
var dec Message
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Sig != nil {
|
||||
m.Sig = *dec.Sig
|
||||
}
|
||||
if dec.TTL != nil {
|
||||
m.TTL = *dec.TTL
|
||||
}
|
||||
if dec.Timestamp != nil {
|
||||
m.Timestamp = *dec.Timestamp
|
||||
}
|
||||
if dec.Topic != nil {
|
||||
m.Topic = *dec.Topic
|
||||
}
|
||||
if dec.Payload != nil {
|
||||
m.Payload = *dec.Payload
|
||||
}
|
||||
if dec.Padding != nil {
|
||||
m.Padding = *dec.Padding
|
||||
}
|
||||
if dec.PoW != nil {
|
||||
m.PoW = *dec.PoW
|
||||
}
|
||||
if dec.Hash != nil {
|
||||
m.Hash = *dec.Hash
|
||||
}
|
||||
if dec.Dst != nil {
|
||||
m.Dst = *dec.Dst
|
||||
}
|
||||
return nil
|
||||
}
|
90
vendor/github.com/status-im/whisper/whisperv6/gen_newmessage_json.go
generated
vendored
Normal file
90
vendor/github.com/status-im/whisper/whisperv6/gen_newmessage_json.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*newMessageOverride)(nil)
|
||||
|
||||
// MarshalJSON marshals type NewMessage to a json string
|
||||
func (n NewMessage) MarshalJSON() ([]byte, error) {
|
||||
type NewMessage struct {
|
||||
SymKeyID string `json:"symKeyID"`
|
||||
PublicKey hexutil.Bytes `json:"pubKey"`
|
||||
Sig string `json:"sig"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Topic TopicType `json:"topic"`
|
||||
Payload hexutil.Bytes `json:"payload"`
|
||||
Padding hexutil.Bytes `json:"padding"`
|
||||
PowTime uint32 `json:"powTime"`
|
||||
PowTarget float64 `json:"powTarget"`
|
||||
TargetPeer string `json:"targetPeer"`
|
||||
}
|
||||
var enc NewMessage
|
||||
enc.SymKeyID = n.SymKeyID
|
||||
enc.PublicKey = n.PublicKey
|
||||
enc.Sig = n.Sig
|
||||
enc.TTL = n.TTL
|
||||
enc.Topic = n.Topic
|
||||
enc.Payload = n.Payload
|
||||
enc.Padding = n.Padding
|
||||
enc.PowTime = n.PowTime
|
||||
enc.PowTarget = n.PowTarget
|
||||
enc.TargetPeer = n.TargetPeer
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals type NewMessage to a json string
|
||||
func (n *NewMessage) UnmarshalJSON(input []byte) error {
|
||||
type NewMessage struct {
|
||||
SymKeyID *string `json:"symKeyID"`
|
||||
PublicKey *hexutil.Bytes `json:"pubKey"`
|
||||
Sig *string `json:"sig"`
|
||||
TTL *uint32 `json:"ttl"`
|
||||
Topic *TopicType `json:"topic"`
|
||||
Payload *hexutil.Bytes `json:"payload"`
|
||||
Padding *hexutil.Bytes `json:"padding"`
|
||||
PowTime *uint32 `json:"powTime"`
|
||||
PowTarget *float64 `json:"powTarget"`
|
||||
TargetPeer *string `json:"targetPeer"`
|
||||
}
|
||||
var dec NewMessage
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.SymKeyID != nil {
|
||||
n.SymKeyID = *dec.SymKeyID
|
||||
}
|
||||
if dec.PublicKey != nil {
|
||||
n.PublicKey = *dec.PublicKey
|
||||
}
|
||||
if dec.Sig != nil {
|
||||
n.Sig = *dec.Sig
|
||||
}
|
||||
if dec.TTL != nil {
|
||||
n.TTL = *dec.TTL
|
||||
}
|
||||
if dec.Topic != nil {
|
||||
n.Topic = *dec.Topic
|
||||
}
|
||||
if dec.Payload != nil {
|
||||
n.Payload = *dec.Payload
|
||||
}
|
||||
if dec.Padding != nil {
|
||||
n.Padding = *dec.Padding
|
||||
}
|
||||
if dec.PowTime != nil {
|
||||
n.PowTime = *dec.PowTime
|
||||
}
|
||||
if dec.PowTarget != nil {
|
||||
n.PowTarget = *dec.PowTarget
|
||||
}
|
||||
if dec.TargetPeer != nil {
|
||||
n.TargetPeer = *dec.TargetPeer
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the Whisper protocol Message element.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
mrand "math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// MessageParams specifies the exact way a message should be wrapped
|
||||
// into an Envelope.
|
||||
type MessageParams struct {
|
||||
TTL uint32
|
||||
Src *ecdsa.PrivateKey
|
||||
Dst *ecdsa.PublicKey
|
||||
KeySym []byte
|
||||
Topic TopicType
|
||||
WorkTime uint32
|
||||
PoW float64
|
||||
Payload []byte
|
||||
Padding []byte
|
||||
}
|
||||
|
||||
// SentMessage represents an end-user data packet to transmit through the
|
||||
// Whisper protocol. These are wrapped into Envelopes that need not be
|
||||
// understood by intermediate nodes, just forwarded.
|
||||
type sentMessage struct {
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
// ReceivedMessage represents a data packet to be received through the
|
||||
// Whisper protocol and successfully decrypted.
|
||||
type ReceivedMessage struct {
|
||||
Raw []byte
|
||||
|
||||
Payload []byte
|
||||
Padding []byte
|
||||
Signature []byte
|
||||
Salt []byte
|
||||
|
||||
PoW float64 // Proof of work as described in the Whisper spec
|
||||
Sent uint32 // Time when the message was posted into the network
|
||||
TTL uint32 // Maximum time to live allowed for the message
|
||||
Src *ecdsa.PublicKey // Message recipient (identity used to decode the message)
|
||||
Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message)
|
||||
Topic TopicType
|
||||
|
||||
SymKeyHash common.Hash // The Keccak256Hash of the key
|
||||
EnvelopeHash common.Hash // Message envelope hash to act as a unique id
|
||||
}
|
||||
|
||||
func isMessageSigned(flags byte) bool {
|
||||
return (flags & signatureFlag) != 0
|
||||
}
|
||||
|
||||
func (msg *ReceivedMessage) isSymmetricEncryption() bool {
|
||||
return msg.SymKeyHash != common.Hash{}
|
||||
}
|
||||
|
||||
func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
|
||||
return msg.Dst != nil
|
||||
}
|
||||
|
||||
// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message.
|
||||
func NewSentMessage(params *MessageParams) (*sentMessage, error) {
|
||||
const payloadSizeFieldMaxSize = 4
|
||||
msg := sentMessage{}
|
||||
msg.Raw = make([]byte, 1,
|
||||
flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
|
||||
msg.Raw[0] = 0 // set all the flags to zero
|
||||
msg.addPayloadSizeField(params.Payload)
|
||||
msg.Raw = append(msg.Raw, params.Payload...)
|
||||
err := msg.appendPadding(params)
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
// addPayloadSizeField appends the auxiliary field containing the size of payload
|
||||
func (msg *sentMessage) addPayloadSizeField(payload []byte) {
|
||||
fieldSize := getSizeOfPayloadSizeField(payload)
|
||||
field := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(field, uint32(len(payload)))
|
||||
field = field[:fieldSize]
|
||||
msg.Raw = append(msg.Raw, field...)
|
||||
msg.Raw[0] |= byte(fieldSize)
|
||||
}
|
||||
|
||||
// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload
|
||||
func getSizeOfPayloadSizeField(payload []byte) int {
|
||||
s := 1
|
||||
for i := len(payload); i >= 256; i /= 256 {
|
||||
s++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// appendPadding appends the padding specified in params.
|
||||
// If no padding is provided in params, then random padding is generated.
|
||||
func (msg *sentMessage) appendPadding(params *MessageParams) error {
|
||||
if len(params.Padding) != 0 {
|
||||
// padding data was provided by the Dapp, just use it as is
|
||||
msg.Raw = append(msg.Raw, params.Padding...)
|
||||
return nil
|
||||
}
|
||||
|
||||
rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload)
|
||||
if params.Src != nil {
|
||||
rawSize += signatureLength
|
||||
}
|
||||
odd := rawSize % padSizeLimit
|
||||
paddingSize := padSizeLimit - odd
|
||||
pad := make([]byte, paddingSize)
|
||||
_, err := crand.Read(pad)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !validateDataIntegrity(pad, paddingSize) {
|
||||
return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize))
|
||||
}
|
||||
msg.Raw = append(msg.Raw, pad...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sign calculates and sets the cryptographic signature for the message,
|
||||
// also setting the sign flag.
|
||||
func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error {
|
||||
if isMessageSigned(msg.Raw[0]) {
|
||||
// this should not happen, but no reason to panic
|
||||
log.Error("failed to sign the message: already signed")
|
||||
return nil
|
||||
}
|
||||
|
||||
msg.Raw[0] |= signatureFlag // it is important to set this flag before signing
|
||||
hash := crypto.Keccak256(msg.Raw)
|
||||
signature, err := crypto.Sign(hash, key)
|
||||
if err != nil {
|
||||
msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag
|
||||
return err
|
||||
}
|
||||
msg.Raw = append(msg.Raw, signature...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptAsymmetric encrypts a message with a public key.
|
||||
func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
|
||||
if !ValidatePublicKey(key) {
|
||||
return errors.New("invalid public key provided for asymmetric encryption")
|
||||
}
|
||||
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
|
||||
if err == nil {
|
||||
msg.Raw = encrypted
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256.
|
||||
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
||||
func (msg *sentMessage) encryptSymmetric(key []byte) (err error) {
|
||||
if !validateDataIntegrity(key, aesKeyLength) {
|
||||
return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key)))
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil)
|
||||
msg.Raw = append(encrypted, salt...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateSecureRandomData generates random data where extra security is required.
|
||||
// The purpose of this function is to prevent some bugs in software or in hardware
|
||||
// from delivering not-very-random data. This is especially useful for AES nonce,
|
||||
// where true randomness does not really matter, but it is very important to have
|
||||
// a unique nonce for every message.
|
||||
func generateSecureRandomData(length int) ([]byte, error) {
|
||||
x := make([]byte, length)
|
||||
y := make([]byte, length)
|
||||
res := make([]byte, length)
|
||||
|
||||
_, err := crand.Read(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !validateDataIntegrity(x, length) {
|
||||
return nil, errors.New("crypto/rand failed to generate secure random data")
|
||||
}
|
||||
_, err = mrand.Read(y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !validateDataIntegrity(y, length) {
|
||||
return nil, errors.New("math/rand failed to generate secure random data")
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
res[i] = x[i] ^ y[i]
|
||||
}
|
||||
if !validateDataIntegrity(res, length) {
|
||||
return nil, errors.New("failed to generate secure random data")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Wrap bundles the message into an Envelope to transmit over the network.
|
||||
func (msg *sentMessage) Wrap(options *MessageParams, now time.Time) (envelope *Envelope, err error) {
|
||||
if options.TTL == 0 {
|
||||
options.TTL = DefaultTTL
|
||||
}
|
||||
if options.Src != nil {
|
||||
if err = msg.sign(options.Src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.Dst != nil {
|
||||
err = msg.encryptAsymmetric(options.Dst)
|
||||
} else if options.KeySym != nil {
|
||||
err = msg.encryptSymmetric(options.KeySym)
|
||||
} else {
|
||||
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envelope = NewEnvelope(options.TTL, options.Topic, msg, now)
|
||||
if err = envelope.Seal(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
|
||||
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
||||
func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
|
||||
// symmetric messages are expected to contain the 12-byte nonce at the end of the payload
|
||||
if len(msg.Raw) < aesNonceLength {
|
||||
return errors.New("missing salt or invalid payload in symmetric message")
|
||||
}
|
||||
salt := msg.Raw[len(msg.Raw)-aesNonceLength:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Raw = decrypted
|
||||
msg.Salt = salt
|
||||
return nil
|
||||
}
|
||||
|
||||
// decryptAsymmetric decrypts an encrypted payload with a private key.
|
||||
func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
|
||||
decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil)
|
||||
if err == nil {
|
||||
msg.Raw = decrypted
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateAndParse checks the message validity and extracts the fields in case of success.
|
||||
func (msg *ReceivedMessage) ValidateAndParse() bool {
|
||||
end := len(msg.Raw)
|
||||
if end < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
if isMessageSigned(msg.Raw[0]) {
|
||||
end -= signatureLength
|
||||
if end <= 1 {
|
||||
return false
|
||||
}
|
||||
msg.Signature = msg.Raw[end : end+signatureLength]
|
||||
msg.Src = msg.SigToPubKey()
|
||||
if msg.Src == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
beg := 1
|
||||
payloadSize := 0
|
||||
sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload
|
||||
if sizeOfPayloadSizeField != 0 {
|
||||
payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField]))
|
||||
if payloadSize+1 > end {
|
||||
return false
|
||||
}
|
||||
beg += sizeOfPayloadSizeField
|
||||
msg.Payload = msg.Raw[beg : beg+payloadSize]
|
||||
}
|
||||
|
||||
beg += payloadSize
|
||||
msg.Padding = msg.Raw[beg:end]
|
||||
return true
|
||||
}
|
||||
|
||||
// SigToPubKey returns the public key associated to the message's
|
||||
// signature.
|
||||
func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
|
||||
defer func() { recover() }() // in case of invalid signature
|
||||
|
||||
pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
|
||||
if err != nil {
|
||||
log.Error("failed to recover public key from signature", "err", err)
|
||||
return nil
|
||||
}
|
||||
return pub
|
||||
}
|
||||
|
||||
// hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding.
|
||||
func (msg *ReceivedMessage) hash() []byte {
|
||||
if isMessageSigned(msg.Raw[0]) {
|
||||
sz := len(msg.Raw) - signatureLength
|
||||
return crypto.Keccak256(msg.Raw[:sz])
|
||||
}
|
||||
return crypto.Keccak256(msg.Raw)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package whisperv6
|
||||
|
||||
import "github.com/ethereum/go-ethereum/metrics"
|
||||
|
||||
var (
|
||||
envelopeAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeAdded", nil)
|
||||
envelopeNewAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeNewAdded", nil)
|
||||
envelopeClearedCounter = metrics.NewRegisteredCounter("whisper/envelopeCleared", nil)
|
||||
envelopeErrFromFutureCounter = metrics.NewRegisteredCounter("whisper/envelopeErrFromFuture", nil)
|
||||
envelopeErrVeryOldCounter = metrics.NewRegisteredCounter("whisper/envelopeErrVeryOld", nil)
|
||||
envelopeErrExpiredCounter = metrics.NewRegisteredCounter("whisper/envelopeErrExpired", nil)
|
||||
envelopeErrOversizedCounter = metrics.NewRegisteredCounter("whisper/envelopeErrOversized", nil)
|
||||
envelopeErrLowPowCounter = metrics.NewRegisteredCounter("whisper/envelopeErrLowPow", nil)
|
||||
envelopeErrNoBloomMatchCounter = metrics.NewRegisteredCounter("whisper/envelopeErrNoBloomMatch", nil)
|
||||
envelopeSizeMeter = metrics.NewRegisteredMeter("whisper/envelopeSize", nil)
|
||||
)
|
|
@ -0,0 +1,268 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Peer represents a whisper protocol peer connection.
|
||||
type Peer struct {
|
||||
host *Whisper
|
||||
peer *p2p.Peer
|
||||
ws p2p.MsgReadWriter
|
||||
|
||||
trusted bool
|
||||
powRequirement float64
|
||||
bloomMu sync.Mutex
|
||||
bloomFilter []byte
|
||||
fullNode bool
|
||||
|
||||
known mapset.Set // Messages already known by the peer to avoid wasting bandwidth
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// newPeer creates a new whisper peer object, but does not run the handshake itself.
|
||||
func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
return &Peer{
|
||||
host: host,
|
||||
peer: remote,
|
||||
ws: rw,
|
||||
trusted: false,
|
||||
powRequirement: 0.0,
|
||||
known: mapset.NewSet(),
|
||||
quit: make(chan struct{}),
|
||||
bloomFilter: MakeFullNodeBloom(),
|
||||
fullNode: true,
|
||||
}
|
||||
}
|
||||
|
||||
// start initiates the peer updater, periodically broadcasting the whisper packets
|
||||
// into the network.
|
||||
func (peer *Peer) start() {
|
||||
go peer.update()
|
||||
log.Trace("start", "peer", peer.ID())
|
||||
}
|
||||
|
||||
// stop terminates the peer updater, stopping message forwarding to it.
|
||||
func (peer *Peer) stop() {
|
||||
close(peer.quit)
|
||||
log.Trace("stop", "peer", peer.ID())
|
||||
}
|
||||
|
||||
// handshake sends the protocol initiation status message to the remote peer and
|
||||
// verifies the remote status too.
|
||||
func (peer *Peer) handshake() error {
|
||||
// Send the handshake status message asynchronously
|
||||
errc := make(chan error, 1)
|
||||
isLightNode := peer.host.LightClientMode()
|
||||
isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted()
|
||||
go func() {
|
||||
pow := peer.host.MinPow()
|
||||
powConverted := math.Float64bits(pow)
|
||||
bloom := peer.host.BloomFilter()
|
||||
|
||||
errc <- p2p.SendItems(peer.ws, statusCode, ProtocolVersion, powConverted, bloom, isLightNode)
|
||||
}()
|
||||
|
||||
// Fetch the remote status packet and verify protocol match
|
||||
packet, err := peer.ws.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packet.Code != statusCode {
|
||||
return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code)
|
||||
}
|
||||
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
|
||||
_, err = s.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err)
|
||||
}
|
||||
peerVersion, err := s.Uint()
|
||||
if err != nil {
|
||||
return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", peer.ID(), err)
|
||||
}
|
||||
if peerVersion != ProtocolVersion {
|
||||
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion)
|
||||
}
|
||||
|
||||
// only version is mandatory, subsequent parameters are optional
|
||||
powRaw, err := s.Uint()
|
||||
if err == nil {
|
||||
pow := math.Float64frombits(powRaw)
|
||||
if math.IsInf(pow, 0) || math.IsNaN(pow) || pow < 0.0 {
|
||||
return fmt.Errorf("peer [%x] sent bad status message: invalid pow", peer.ID())
|
||||
}
|
||||
peer.powRequirement = pow
|
||||
|
||||
var bloom []byte
|
||||
err = s.Decode(&bloom)
|
||||
if err == nil {
|
||||
sz := len(bloom)
|
||||
if sz != BloomFilterSize && sz != 0 {
|
||||
return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz)
|
||||
}
|
||||
peer.setBloomFilter(bloom)
|
||||
}
|
||||
}
|
||||
|
||||
isRemotePeerLightNode, err := s.Bool()
|
||||
if isRemotePeerLightNode && isLightNode && isRestrictedLightNodeConnection {
|
||||
return fmt.Errorf("peer [%x] is useless: two light client communication restricted", peer.ID())
|
||||
}
|
||||
|
||||
if err := <-errc; err != nil {
|
||||
return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// update executes periodic operations on the peer, including message transmission
|
||||
// and expiration.
|
||||
func (peer *Peer) update() {
|
||||
// Start the tickers for the updates
|
||||
expire := time.NewTicker(expirationCycle)
|
||||
transmit := time.NewTicker(transmissionCycle)
|
||||
|
||||
// Loop and transmit until termination is requested
|
||||
for {
|
||||
select {
|
||||
case <-expire.C:
|
||||
peer.expire()
|
||||
|
||||
case <-transmit.C:
|
||||
if err := peer.broadcast(); err != nil {
|
||||
log.Trace("broadcast failed", "reason", err, "peer", peer.ID())
|
||||
return
|
||||
}
|
||||
|
||||
case <-peer.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark marks an envelope known to the peer so that it won't be sent back.
|
||||
func (peer *Peer) mark(envelope *Envelope) {
|
||||
peer.known.Add(envelope.Hash())
|
||||
}
|
||||
|
||||
// marked checks if an envelope is already known to the remote peer.
|
||||
func (peer *Peer) marked(envelope *Envelope) bool {
|
||||
return peer.known.Contains(envelope.Hash())
|
||||
}
|
||||
|
||||
// expire iterates over all the known envelopes in the host and removes all
|
||||
// expired (unknown) ones from the known list.
|
||||
func (peer *Peer) expire() {
|
||||
unmark := make(map[common.Hash]struct{})
|
||||
peer.known.Each(func(v interface{}) bool {
|
||||
if !peer.host.isEnvelopeCached(v.(common.Hash)) {
|
||||
unmark[v.(common.Hash)] = struct{}{}
|
||||
}
|
||||
return true
|
||||
})
|
||||
// Dump all known but no longer cached
|
||||
for hash := range unmark {
|
||||
peer.known.Remove(hash)
|
||||
}
|
||||
}
|
||||
|
||||
// broadcast iterates over the collection of envelopes and transmits yet unknown
|
||||
// ones over the network.
|
||||
func (peer *Peer) broadcast() error {
|
||||
if peer.peer.IsFlaky() {
|
||||
log.Trace("Waiting for a peer to restore communication", "ID", peer.peer.ID())
|
||||
return nil
|
||||
}
|
||||
envelopes := peer.host.Envelopes()
|
||||
bundle := make([]*Envelope, 0, len(envelopes))
|
||||
for _, envelope := range envelopes {
|
||||
if !peer.marked(envelope) && envelope.PoW() >= peer.powRequirement && peer.bloomMatch(envelope) {
|
||||
bundle = append(bundle, envelope)
|
||||
}
|
||||
}
|
||||
|
||||
if len(bundle) > 0 {
|
||||
// transmit the batch of envelopes
|
||||
if err := p2p.Send(peer.ws, messagesCode, bundle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// mark envelopes only if they were successfully sent
|
||||
for _, e := range bundle {
|
||||
peer.mark(e)
|
||||
peer.host.envelopeFeed.Send(EnvelopeEvent{
|
||||
Event: EventEnvelopeSent,
|
||||
Hash: e.Hash(),
|
||||
Peer: peer.peer.ID(), // specifically discover.NodeID because it can be pretty printed
|
||||
})
|
||||
}
|
||||
|
||||
log.Trace("broadcast", "num. messages", len(bundle))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns a peer's id
|
||||
func (peer *Peer) ID() []byte {
|
||||
id := peer.peer.ID()
|
||||
return id[:]
|
||||
}
|
||||
|
||||
func (peer *Peer) notifyAboutPowRequirementChange(pow float64) error {
|
||||
i := math.Float64bits(pow)
|
||||
return p2p.Send(peer.ws, powRequirementCode, i)
|
||||
}
|
||||
|
||||
func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error {
|
||||
return p2p.Send(peer.ws, bloomFilterExCode, bloom)
|
||||
}
|
||||
|
||||
func (peer *Peer) bloomMatch(env *Envelope) bool {
|
||||
peer.bloomMu.Lock()
|
||||
defer peer.bloomMu.Unlock()
|
||||
return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom())
|
||||
}
|
||||
|
||||
func (peer *Peer) setBloomFilter(bloom []byte) {
|
||||
peer.bloomMu.Lock()
|
||||
defer peer.bloomMu.Unlock()
|
||||
peer.bloomFilter = bloom
|
||||
peer.fullNode = isFullNode(bloom)
|
||||
if peer.fullNode && peer.bloomFilter == nil {
|
||||
peer.bloomFilter = MakeFullNodeBloom()
|
||||
}
|
||||
}
|
||||
|
||||
func MakeFullNodeBloom() []byte {
|
||||
bloom := make([]byte, BloomFilterSize)
|
||||
for i := 0; i < BloomFilterSize; i++ {
|
||||
bloom[i] = 0xFF
|
||||
}
|
||||
return bloom
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the Whisper protocol Topic element.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// TopicType represents a cryptographically secure, probabilistic partial
|
||||
// classifications of a message, determined as the first (left) 4 bytes of the
|
||||
// SHA3 hash of some arbitrary data given by the original author of the message.
|
||||
type TopicType [TopicLength]byte
|
||||
|
||||
// BytesToTopic converts from the byte array representation of a topic
|
||||
// into the TopicType type.
|
||||
func BytesToTopic(b []byte) (t TopicType) {
|
||||
sz := TopicLength
|
||||
if x := len(b); x < TopicLength {
|
||||
sz = x
|
||||
}
|
||||
for i := 0; i < sz; i++ {
|
||||
t[i] = b[i]
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// String converts a topic byte array to a string representation.
|
||||
func (t *TopicType) String() string {
|
||||
return common.ToHex(t[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of t.
|
||||
func (t TopicType) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(t[:]).MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText parses a hex representation to a topic.
|
||||
func (t *TopicType) UnmarshalText(input []byte) error {
|
||||
return hexutil.UnmarshalFixedText("Topic", input, t[:])
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue