whisper/whisperv6: initial commit (clone of v5) (#15324)

This commit is contained in:
gluk256 2017-11-03 21:29:49 +01:00 committed by Felix Lange
parent 0131bd6ff9
commit 9f7cd75682
18 changed files with 5589 additions and 0 deletions

591
whisper/whisperv6/api.go Normal file
View File

@ -0,0 +1,591 @@
// 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"
)
const (
filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds
)
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),
}
go api.run()
return api
}
// run the api event loop.
// this loop deletes filter that have not been used within filterTimeout
func (api *PublicWhisperAPI) run() {
timeout := time.NewTicker(2 * time.Minute)
for {
<-timeout.C
api.mu.Lock()
for id, lastUsed := range api.lastUsed {
if time.Since(lastUsed).Seconds() >= filterTimeout {
delete(api.lastUsed, id)
if err := api.w.Unsubscribe(id); err != nil {
log.Error("could not unsubscribe whisper filter", "error", err)
}
log.Debug("delete whisper filter (timeout)", "id", id)
}
}
api.mu.Unlock()
}
}
// 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 for a message before it is accepted.
func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
return true, api.w.SetMinimumPoW(pow)
}
// 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
}
// GetPublicKey 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)
}
//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 a message on the Whisper network.
func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, 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 false, 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 false, err
}
}
// Set symmetric key that is used to encrypt the message
if symKeyGiven {
if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption
return false, ErrNoTopics
}
if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
return false, err
}
if !validateSymmetricKey(params.KeySym) {
return false, ErrInvalidSymmetricKey
}
}
// Set asymmetric key that is used to encrypt the message
if pubKeyGiven {
params.Dst = crypto.ToECDSAPub(req.PublicKey)
if !ValidatePublicKey(params.Dst) {
return false, ErrInvalidPublicKey
}
}
// encrypt and sent message
whisperMsg, err := NewSentMessage(params)
if err != nil {
return false, err
}
env, err := whisperMsg.Wrap(params)
if err != nil {
return false, err
}
// send to specific node (skip PoW check)
if len(req.TargetPeer) > 0 {
n, err := discover.ParseNode(req.TargetPeer)
if err != nil {
return false, fmt.Errorf("failed to parse target peer: %s", err)
}
return true, api.w.SendP2PMessage(n.ID[:], env)
}
// ensure that the message PoW meets the node's minimum accepted PoW
if req.PowTarget < api.w.MinPow() {
return false, ErrTooLowPoW
}
return true, api.w.Send(env)
}
//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 {
filter.Src = crypto.ToECDSAPub(crit.Sig)
if !ValidatePublicKey(filter.Src) {
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 !validateSymmetricKey(key) {
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 {
src = crypto.ToECDSAPub(req.Sig)
if !ValidatePublicKey(src) {
return "", ErrInvalidSigningPubKey
}
}
if symKeyGiven {
if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
return "", err
}
if !validateSymmetricKey(keySym) {
return "", ErrInvalidSymmetricKey
}
}
if asymKeyGiven {
if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
return "", err
}
}
if len(req.Topics) > 0 {
topics = make([][]byte, 1)
for _, topic := range req.Topics {
topics = append(topics, 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
}

View File

@ -0,0 +1,206 @@
// 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 (
"testing"
"github.com/ethereum/go-ethereum/crypto"
)
func BenchmarkDeriveKeyMaterial(b *testing.B) {
for i := 0; i < b.N; i++ {
deriveKeyMaterial([]byte("test"), 0)
}
}
func BenchmarkEncryptionSym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Errorf("failed Wrap with seed %d: %s.", seed, err)
b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload))
return
}
}
}
func BenchmarkEncryptionAsym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}
func BenchmarkDecryptionSymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: params.KeySym}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("failed to open with seed %d.", seed)
}
}
}
func BenchmarkDecryptionSymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: []byte("arbitrary stuff here")}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("fail to open, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
key, err = crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func increment(x []byte) {
for i := 0; i < len(x); i++ {
x[i]++
if x[i] != 0 {
break
}
}
}
func BenchmarkPoW(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.Payload = make([]byte, 32)
params.PoW = 10.0
params.TTL = 1
for i := 0; i < b.N; i++ {
increment(params.Payload)
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}

View File

@ -0,0 +1,27 @@
// 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
type Config struct {
MaxMessageSize uint32 `toml:",omitempty"`
MinimumAcceptedPOW float64 `toml:",omitempty"`
}
var DefaultConfig = Config{
MaxMessageSize: DefaultMaxMessageSize,
MinimumAcceptedPOW: DefaultMinimumPoW,
}

87
whisper/whisperv6/doc.go Normal file
View File

@ -0,0 +1,87 @@
// 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.
*/
package whisperv6
import (
"fmt"
"time"
)
const (
EnvelopeVersion = uint64(0)
ProtocolVersion = uint64(5)
ProtocolVersionStr = "5.0"
ProtocolName = "shh"
statusCode = 0 // used by whisper protocol
messagesCode = 1 // normal whisper message
p2pCode = 2 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
p2pRequestCode = 3 // peer-to-peer message, used by Dapp protocol
NumberOfMessageCodes = 64
paddingMask = byte(3)
signatureFlag = byte(4)
TopicLength = 4
signatureLength = 65
aesKeyLength = 32
AESNonceLength = 12
keyIdSize = 32
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 (must not exceed 2^24)
messageQueueLimit = 1024
expirationCycle = time.Second
transmissionCycle = 300 * time.Millisecond
DefaultTTL = 50 // seconds
SynchAllowance = 10 // seconds
)
type unknownVersionError uint64
func (e unknownVersionError) Error() string {
return fmt.Sprintf("invalid envelope version %d", uint64(e))
}
// 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)
}

View File

@ -0,0 +1,246 @@
// 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 {
Version []byte
Expiry uint32
TTL uint32
Topic TopicType
AESNonce []byte
Data []byte
EnvNonce uint64
pow float64 // Message-specific PoW as described in the Whisper specification.
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
// Don't access hash directly, use Hash() function instead.
}
// size returns the size of envelope as it is sent (i.e. public fields only)
func (e *Envelope) size() int {
return 20 + len(e.Version) + len(e.AESNonce) + len(e.Data)
}
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
func (e *Envelope) rlpWithoutNonce() []byte {
res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.AESNonce, 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, aesNonce []byte, msg *sentMessage) *Envelope {
env := Envelope{
Version: make([]byte, 1),
Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
TTL: ttl,
Topic: topic,
AESNonce: aesNonce,
Data: msg.Raw,
EnvNonce: 0,
}
if EnvelopeVersion < 256 {
env.Version[0] = byte(EnvelopeVersion)
} else {
panic("please increase the size of Envelope.Version before releasing this version")
}
return &env
}
func (e *Envelope) IsSymmetric() bool {
return len(e.AESNonce) > 0
}
func (e *Envelope) isAsymmetric() bool {
return !e.IsSymmetric()
}
func (e *Envelope) Ver() uint64 {
return bytesToUintLittleEndian(e.Version)
}
// 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 {
var target, bestBit int
if options.PoW == 0 {
// adjust for the duration of Seal() execution only if execution time is predefined unconditionally
e.Expiry += options.WorkTime
} else {
target = e.powToFirstBit(options.PoW)
if target < 1 {
target = 1
}
}
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.EnvNonce, 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
}
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.EnvNonce)
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)
return int(bits)
}
// 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, e.AESNonce)
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 e.isAsymmetric() {
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
if msg != nil {
msg.Dst = &watcher.KeyAsym.PublicKey
}
} else if e.IsSymmetric() {
msg, _ = e.OpenSymmetric(watcher.KeySym)
if msg != nil {
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
}
}
if msg != nil {
ok := msg.Validate()
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()
msg.EnvelopeVersion = e.Ver()
}
return msg
}

239
whisper/whisperv6/filter.go Normal file
View File

@ -0,0 +1,239 @@
// 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"
)
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
Messages map[common.Hash]*ReceivedMessage
mutex sync.RWMutex
}
type Filters struct {
watchers map[string]*Filter
whisper *Whisper
mutex sync.RWMutex
}
func NewFilters(w *Whisper) *Filters {
return &Filters{
watchers: make(map[string]*Filter),
whisper: w,
}
}
func (fs *Filters) Install(watcher *Filter) (string, error) {
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)
}
fs.watchers[id] = watcher
return id, err
}
func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
delete(fs.watchers, id)
return true
}
return false
}
func (fs *Filters) Get(id string) *Filter {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
return fs.watchers[id]
}
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
var msg *ReceivedMessage
fs.mutex.RLock()
defer fs.mutex.RUnlock()
i := -1 // only used for logging info
for _, watcher := range fs.watchers {
i++
if p2pMessage && !watcher.AllowP2P {
log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
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", i)
}
} else {
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
}
}
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) processEnvelope(env *Envelope) *ReceivedMessage {
if f.MatchEnvelope(env) {
msg := env.Open(f)
if msg != nil {
return msg
} else {
log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex())
}
} else {
log.Trace("processing envelope: does not match", "hash", env.Hash().Hex())
}
return nil
}
func (f *Filter) expectsAsymmetricEncryption() bool {
return f.KeyAsym != nil
}
func (f *Filter) expectsSymmetricEncryption() bool {
return f.KeySym != nil
}
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
}
}
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
}
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) && f.MatchTopic(msg.Topic)
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
return f.SymKeyHash == msg.SymKeyHash && f.MatchTopic(msg.Topic)
}
return false
}
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
if f.PoW > 0 && envelope.pow < f.PoW {
return false
}
if f.expectsAsymmetricEncryption() && envelope.isAsymmetric() {
return f.MatchTopic(envelope.Topic)
} else if f.expectsSymmetricEncryption() && envelope.IsSymmetric() {
return f.MatchTopic(envelope.Topic)
}
return false
}
func (f *Filter) MatchTopic(topic TopicType) bool {
if len(f.Topics) == 0 {
// any topic matches
return true
}
for _, bt := range f.Topics {
if matchSingleTopic(topic, bt) {
return true
}
}
return false
}
func matchSingleTopic(topic TopicType, bt []byte) bool {
if len(bt) > 4 {
bt = bt[:4]
}
for j, b := range bt {
if topic[j] != b {
return false
}
}
return true
}
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
}

View File

@ -0,0 +1,814 @@
// 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 (
"math/big"
mrand "math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
var seed int64
// InitSingleTest should be called in the beginning of every
// test, which uses RNG, in order to make the tests
// reproduciblity independent of their sequence.
func InitSingleTest() {
seed = time.Now().Unix()
mrand.Seed(seed)
}
func InitDebugTest(i int64) {
seed = i
mrand.Seed(seed)
}
type FilterTestCase struct {
f *Filter
id string
alive bool
msgCnt int
}
func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
var f Filter
f.Messages = make(map[common.Hash]*ReceivedMessage)
const topicNum = 8
f.Topics = make([][]byte, topicNum)
for i := 0; i < topicNum; i++ {
f.Topics[i] = make([]byte, 4)
mrand.Read(f.Topics[i][:])
f.Topics[i][0] = 0x01
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("generateFilter 1 failed with seed %d.", seed)
return nil, err
}
f.Src = &key.PublicKey
if symmetric {
f.KeySym = make([]byte, aesKeyLength)
mrand.Read(f.KeySym)
f.SymKeyHash = crypto.Keccak256Hash(f.KeySym)
} else {
f.KeyAsym, err = crypto.GenerateKey()
if err != nil {
t.Fatalf("generateFilter 2 failed with seed %d.", seed)
return nil, err
}
}
// AcceptP2P & PoW are not set
return &f, nil
}
func generateTestCases(t *testing.T, SizeTestFilters int) []FilterTestCase {
cases := make([]FilterTestCase, SizeTestFilters)
for i := 0; i < SizeTestFilters; i++ {
f, _ := generateFilter(t, true)
cases[i].f = f
cases[i].alive = (mrand.Int()&int(1) == 0)
}
return cases
}
func TestInstallFilters(t *testing.T) {
InitSingleTest()
const SizeTestFilters = 256
w := New(&Config{})
filters := NewFilters(w)
tst := generateTestCases(t, SizeTestFilters)
var err error
var j string
for i := 0; i < SizeTestFilters; i++ {
j, err = filters.Install(tst[i].f)
if err != nil {
t.Fatalf("seed %d: failed to install filter: %s", seed, err)
}
tst[i].id = j
if len(j) != keyIdSize*2 {
t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
}
}
for _, testCase := range tst {
if !testCase.alive {
filters.Uninstall(testCase.id)
}
}
for i, testCase := range tst {
fil := filters.Get(testCase.id)
exist := (fil != nil)
if exist != testCase.alive {
t.Fatalf("seed %d: failed alive: %d, %v, %v", seed, i, exist, testCase.alive)
}
if exist && fil.PoW != testCase.f.PoW {
t.Fatalf("seed %d: failed Get: %d, %v, %v", seed, i, exist, testCase.alive)
}
}
}
func TestInstallSymKeyGeneratesHash(t *testing.T) {
InitSingleTest()
w := New(&Config{})
filters := NewFilters(w)
filter, _ := generateFilter(t, true)
// save the current SymKeyHash for comparison
initialSymKeyHash := filter.SymKeyHash
// ensure the SymKeyHash is invalid, for Install to recreate it
var invalid common.Hash
filter.SymKeyHash = invalid
_, err := filters.Install(filter)
if err != nil {
t.Fatalf("Error installing the filter: %s", err)
}
for i, b := range filter.SymKeyHash {
if b != initialSymKeyHash[i] {
t.Fatalf("The filter's symmetric key hash was not properly generated by Install")
}
}
}
func TestInstallIdenticalFilters(t *testing.T) {
InitSingleTest()
w := New(&Config{})
filters := NewFilters(w)
filter1, _ := generateFilter(t, true)
// Copy the first filter since some of its fields
// are randomly gnerated.
filter2 := &Filter{
KeySym: filter1.KeySym,
Topics: filter1.Topics,
PoW: filter1.PoW,
AllowP2P: filter1.AllowP2P,
Messages: make(map[common.Hash]*ReceivedMessage),
}
_, err := filters.Install(filter1)
if err != nil {
t.Fatalf("Error installing the first filter with seed %d: %s", seed, err)
}
_, err = filters.Install(filter2)
if err != nil {
t.Fatalf("Error installing the second filter with seed %d: %s", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("Error generating message parameters with seed %d: %s", seed, err)
}
params.KeySym = filter1.KeySym
params.Topic = BytesToTopic(filter1.Topics[0])
filter1.Src = &params.Src.PublicKey
filter2.Src = &params.Src.PublicKey
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(filter1)
if msg == nil {
t.Fatalf("failed to Open with filter1")
}
if !filter1.MatchEnvelope(env) {
t.Fatalf("failed matching with the first filter")
}
if !filter2.MatchEnvelope(env) {
t.Fatalf("failed matching with the first filter")
}
if !filter1.MatchMessage(msg) {
t.Fatalf("failed matching with the second filter")
}
if !filter2.MatchMessage(msg) {
t.Fatalf("failed matching with the second filter")
}
}
func TestComparePubKey(t *testing.T) {
InitSingleTest()
key1, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate first key with seed %d: %s.", seed, err)
}
key2, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate second key with seed %d: %s.", seed, err)
}
if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) {
t.Fatalf("public keys are equal, seed %d.", seed)
}
// generate key3 == key1
mrand.Seed(seed)
key3, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate third key with seed %d: %s.", seed, err)
}
if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) {
t.Fatalf("key1 == key3, seed %d.", seed)
}
}
func TestMatchEnvelope(t *testing.T) {
InitSingleTest()
fsym, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
fasym, err := generateFilter(t, false)
if err != nil {
t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.Topic[0] = 0xFF // ensure mismatch
// mismatch with pseudo-random data
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
match := fsym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope symmetric with seed %d.", seed)
}
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope asymmetric with seed %d.", seed)
}
// encrypt symmetrically
i := mrand.Int() % 4
fsym.Topics[i] = params.Topic[:]
fasym.Topics[i] = params.Topic[:]
msg, err = NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err = msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap() with seed %d: %s.", seed, err)
}
// symmetric + matching topic: match
match = fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed)
}
// asymmetric + matching topic: mismatch
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope() asymmetric with seed %d.", seed)
}
// symmetric + matching topic + insufficient PoW: mismatch
fsym.PoW = env.PoW() + 1.0
match = fsym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(symmetric + matching topic + insufficient PoW) asymmetric with seed %d.", seed)
}
// symmetric + matching topic + sufficient PoW: match
fsym.PoW = env.PoW() / 2
match = fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(symmetric + matching topic + sufficient PoW) with seed %d.", seed)
}
// symmetric + topics are nil (wildcard): match
prevTopics := fsym.Topics
fsym.Topics = nil
match = fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(symmetric + topics are nil) with seed %d.", seed)
}
fsym.Topics = prevTopics
// encrypt asymmetrically
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, err = NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err = msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap() with seed %d: %s.", seed, err)
}
// encryption method mismatch
match = fsym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
// asymmetric + mismatching topic: mismatch
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + mismatching topic) with seed %d.", seed)
}
// asymmetric + matching topic: match
fasym.Topics[i] = fasym.Topics[i+1]
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(asymmetric + matching topic) with seed %d.", seed)
}
// asymmetric + filter without topic (wildcard): match
fasym.Topics = nil
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + filter without topic) with seed %d.", seed)
}
// asymmetric + insufficient PoW: mismatch
fasym.PoW = env.PoW() + 1.0
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(asymmetric + insufficient PoW) with seed %d.", seed)
}
// asymmetric + sufficient PoW: match
fasym.PoW = env.PoW() / 2
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + sufficient PoW) with seed %d.", seed)
}
// filter without topic + envelope without topic: match
env.Topic = TopicType{}
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
}
// filter with topic + envelope without topic: mismatch
fasym.Topics = fsym.Topics
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
}
}
func TestMatchMessageSym(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
const index = 1
params.KeySym = f.KeySym
params.Topic = BytesToTopic(f.Topics[index])
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(f)
if msg == nil {
t.Fatalf("failed Open with seed %d.", seed)
}
// Src: match
*f.Src.X = *params.Src.PublicKey.X
*f.Src.Y = *params.Src.PublicKey.Y
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src match) with seed %d.", seed)
}
// insufficient PoW: mismatch
f.PoW = msg.PoW + 1.0
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
}
// sufficient PoW: match
f.PoW = msg.PoW / 2
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
}
// topic mismatch
f.Topics[index][0]++
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
}
f.Topics[index][0]--
// key mismatch
f.SymKeyHash[0]++
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
}
f.SymKeyHash[0]--
// Src absent: match
f.Src = nil
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
}
// key hash mismatch
h := f.SymKeyHash
f.SymKeyHash = common.Hash{}
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key hash mismatch) with seed %d.", seed)
}
f.SymKeyHash = h
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key hash match) with seed %d.", seed)
}
// encryption method mismatch
f.KeySym = nil
f.KeyAsym, err = crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
}
func TestMatchMessageAsym(t *testing.T) {
InitSingleTest()
f, err := generateFilter(t, false)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
const index = 1
params.Topic = BytesToTopic(f.Topics[index])
params.Dst = &f.KeyAsym.PublicKey
keySymOrig := params.KeySym
params.KeySym = nil
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(f)
if msg == nil {
t.Fatalf("failed to open with seed %d.", seed)
}
// Src: match
*f.Src.X = *params.Src.PublicKey.X
*f.Src.Y = *params.Src.PublicKey.Y
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchMessage(src match) with seed %d.", seed)
}
// insufficient PoW: mismatch
f.PoW = msg.PoW + 1.0
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
}
// sufficient PoW: match
f.PoW = msg.PoW / 2
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
}
// topic mismatch
f.Topics[index][0]++
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
}
f.Topics[index][0]--
// key mismatch
prev := *f.KeyAsym.PublicKey.X
zero := *big.NewInt(0)
*f.KeyAsym.PublicKey.X = zero
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
}
*f.KeyAsym.PublicKey.X = prev
// Src absent: match
f.Src = nil
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
}
// encryption method mismatch
f.KeySym = keySymOrig
f.KeyAsym = nil
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
}
func cloneFilter(orig *Filter) *Filter {
var clone Filter
clone.Messages = make(map[common.Hash]*ReceivedMessage)
clone.Src = orig.Src
clone.KeyAsym = orig.KeyAsym
clone.KeySym = orig.KeySym
clone.Topics = orig.Topics
clone.PoW = orig.PoW
clone.AllowP2P = orig.AllowP2P
clone.SymKeyHash = orig.SymKeyHash
return &clone
}
func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
return nil
}
params.KeySym = f.KeySym
params.Topic = BytesToTopic(f.Topics[2])
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
return nil
}
return env
}
func TestWatchers(t *testing.T) {
InitSingleTest()
const NumFilters = 16
const NumMessages = 256
var i int
var j uint32
var e *Envelope
var x, firstID string
var err error
w := New(&Config{})
filters := NewFilters(w)
tst := generateTestCases(t, NumFilters)
for i = 0; i < NumFilters; i++ {
tst[i].f.Src = nil
x, err = filters.Install(tst[i].f)
if err != nil {
t.Fatalf("failed to install filter with seed %d: %s.", seed, err)
}
tst[i].id = x
if len(firstID) == 0 {
firstID = x
}
}
lastID := x
var envelopes [NumMessages]*Envelope
for i = 0; i < NumMessages; i++ {
j = mrand.Uint32() % NumFilters
e = generateCompatibeEnvelope(t, tst[j].f)
envelopes[i] = e
tst[j].msgCnt++
}
for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], false)
}
var total int
var mail []*ReceivedMessage
var count [NumFilters]int
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
count[i] = len(mail)
total += len(mail)
}
if total != NumMessages {
t.Fatalf("failed with seed %d: total = %d, want: %d.", seed, total, NumMessages)
}
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
if len(mail) != 0 {
t.Fatalf("failed with seed %d: i = %d.", seed, i)
}
if tst[i].msgCnt != count[i] {
t.Fatalf("failed with seed %d: count[%d]: get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
}
}
// another round with a cloned filter
clone := cloneFilter(tst[0].f)
filters.Uninstall(lastID)
total = 0
last := NumFilters - 1
tst[last].f = clone
filters.Install(clone)
for i = 0; i < NumFilters; i++ {
tst[i].msgCnt = 0
count[i] = 0
}
// make sure that the first watcher receives at least one message
e = generateCompatibeEnvelope(t, tst[0].f)
envelopes[0] = e
tst[0].msgCnt++
for i = 1; i < NumMessages; i++ {
j = mrand.Uint32() % NumFilters
e = generateCompatibeEnvelope(t, tst[j].f)
envelopes[i] = e
tst[j].msgCnt++
}
for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], false)
}
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
count[i] = len(mail)
total += len(mail)
}
combined := tst[0].msgCnt + tst[last].msgCnt
if total != NumMessages+count[0] {
t.Fatalf("failed with seed %d: total = %d, count[0] = %d.", seed, total, count[0])
}
if combined != count[0] {
t.Fatalf("failed with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0])
}
if combined != count[last] {
t.Fatalf("failed with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last])
}
for i = 1; i < NumFilters-1; i++ {
mail = tst[i].f.Retrieve()
if len(mail) != 0 {
t.Fatalf("failed with seed %d: i = %d.", seed, i)
}
if tst[i].msgCnt != count[i] {
t.Fatalf("failed with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
}
}
// test AcceptP2P
total = 0
filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
total += len(mail)
}
if total != 0 {
t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total)
}
f := filters.Get(firstID)
if f == nil {
t.Fatalf("failed to get the filter with seed %d.", seed)
}
f.AllowP2P = true
total = 0
filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
total += len(mail)
}
if total != 1 {
t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total)
}
}
func TestVariableTopics(t *testing.T) {
InitSingleTest()
var match bool
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
for i := 0; i < 4; i++ {
arr := make([]byte, i+1, 4)
copy(arr, env.Topic[:i+1])
f.Topics[4] = arr
match = f.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i)
}
f.Topics[4][i]++
match = f.MatchEnvelope(env)
if match {
t.Fatalf("MatchEnvelope symmetric with seed %d, step %d: false positive.", seed, i)
}
}
}

View File

@ -0,0 +1,64 @@
// 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)
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)
}
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
}

View File

@ -0,0 +1,82 @@
// 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)
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)
}
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
}

View File

@ -0,0 +1,88 @@
// 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)
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)
}
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
}

View File

@ -0,0 +1,352 @@
// 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"
"strconv"
"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"
)
// Options 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.
type ReceivedMessage struct {
Raw []byte
Payload []byte
Padding []byte
Signature []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, associated with the Topic
EnvelopeHash common.Hash // Message envelope hash to act as a unique id
EnvelopeVersion uint64
}
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
}
// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
func NewSentMessage(params *MessageParams) (*sentMessage, error) {
msg := sentMessage{}
msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
msg.Raw[0] = 0 // set all the flags to zero
err := msg.appendPadding(params)
if err != nil {
return nil, err
}
msg.Raw = append(msg.Raw, params.Payload...)
return &msg, nil
}
// getSizeOfLength returns the number of bytes necessary to encode the entire size padding (including these bytes)
func getSizeOfLength(b []byte) (sz int, err error) {
sz = intSize(len(b)) // first iteration
sz = intSize(len(b) + sz) // second iteration
if sz > 3 {
err = errors.New("oversized padding parameter")
}
return sz, err
}
// sizeOfIntSize returns minimal number of bytes necessary to encode an integer value
func intSize(i int) (s int) {
for s = 1; i >= 256; s++ {
i /= 256
}
return s
}
// appendPadding appends the pseudorandom padding bytes and sets the padding flag.
// The last byte contains the size of padding (thus, its size must not exceed 256).
func (msg *sentMessage) appendPadding(params *MessageParams) error {
rawSize := len(params.Payload) + 1
if params.Src != nil {
rawSize += signatureLength
}
odd := rawSize % padSizeLimit
if len(params.Padding) != 0 {
padSize := len(params.Padding)
padLengthSize, err := getSizeOfLength(params.Padding)
if err != nil {
return err
}
totalPadSize := padSize + padLengthSize
buf := make([]byte, 8)
binary.LittleEndian.PutUint32(buf, uint32(totalPadSize))
buf = buf[:padLengthSize]
msg.Raw = append(msg.Raw, buf...)
msg.Raw = append(msg.Raw, params.Padding...)
msg.Raw[0] |= byte(padLengthSize) // number of bytes indicating the padding size
} else if odd != 0 {
totalPadSize := padSizeLimit - odd
if totalPadSize > 255 {
// this algorithm is only valid if padSizeLimit < 256.
// if padSizeLimit will ever change, please fix the algorithm
// (please see also ReceivedMessage.extractPadding() function).
panic("please fix the padding algorithm before releasing new version")
}
buf := make([]byte, totalPadSize)
_, err := crand.Read(buf[1:])
if err != nil {
return err
}
if totalPadSize > 6 && !validateSymmetricKey(buf) {
return errors.New("failed to generate random padding of size " + strconv.Itoa(totalPadSize))
}
buf[0] = byte(totalPadSize)
msg.Raw = append(msg.Raw, buf...)
msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
}
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
hash := crypto.Keccak256(msg.Raw)
signature, err := crypto.Sign(hash, key)
if err != nil {
msg.Raw[0] &= ^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) (nonce []byte, err error) {
if !validateSymmetricKey(key) {
return nil, errors.New("invalid key provided for symmetric encryption")
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// never use more than 2^32 random nonces with a given key
nonce = make([]byte, aesgcm.NonceSize())
_, err = crand.Read(nonce)
if err != nil {
return nil, err
} else if !validateSymmetricKey(nonce) {
return nil, errors.New("crypto/rand failed to generate nonce")
}
msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil)
return nonce, nil
}
// Wrap bundles the message into an Envelope to transmit over the network.
func (msg *sentMessage) Wrap(options *MessageParams) (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
}
}
var nonce []byte
if options.Dst != nil {
err = msg.encryptAsymmetric(options.Dst)
} else if options.KeySym != nil {
nonce, 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, nonce, msg)
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, nonce []byte) error {
block, err := aes.NewCipher(key)
if err != nil {
return err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
if len(nonce) != aesgcm.NonceSize() {
log.Error("decrypting the message", "AES nonce size", len(nonce))
return errors.New("wrong AES nonce size")
}
decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
if err != nil {
return err
}
msg.Raw = decrypted
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(crand.Reader, msg.Raw, nil, nil)
if err == nil {
msg.Raw = decrypted
}
return err
}
// Validate checks the validity and extracts the fields in case of success
func (msg *ReceivedMessage) Validate() 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:]
msg.Src = msg.SigToPubKey()
if msg.Src == nil {
return false
}
}
padSize, ok := msg.extractPadding(end)
if !ok {
return false
}
msg.Payload = msg.Raw[1+padSize : end]
return true
}
// extractPadding extracts the padding from raw message.
// although we don't support sending messages with padding size
// exceeding 255 bytes, such messages are perfectly valid, and
// can be successfully decrypted.
func (msg *ReceivedMessage) extractPadding(end int) (int, bool) {
paddingSize := 0
sz := int(msg.Raw[0] & paddingMask) // number of bytes indicating the entire size of padding (including these bytes)
// could be zero -- it means no padding
if sz != 0 {
paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz]))
if paddingSize < sz || paddingSize+1 > end {
return 0, false
}
msg.Padding = msg.Raw[1+sz : 1+paddingSize]
}
return paddingSize, true
}
// Recover retrieves the public key of the message signer.
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 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)
}

View File

@ -0,0 +1,415 @@
// 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 (
"bytes"
mrand "math/rand"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
func generateMessageParams() (*MessageParams, error) {
// set all the parameters except p.Dst and p.Padding
buf := make([]byte, 4)
mrand.Read(buf)
sz := mrand.Intn(400)
var p MessageParams
p.PoW = 0.01
p.WorkTime = 1
p.TTL = uint32(mrand.Intn(1024))
p.Payload = make([]byte, sz)
p.KeySym = make([]byte, aesKeyLength)
mrand.Read(p.Payload)
mrand.Read(p.KeySym)
p.Topic = BytesToTopic(buf)
var err error
p.Src, err = crypto.GenerateKey()
if err != nil {
return nil, err
}
return &p, nil
}
func singleMessageTest(t *testing.T, symmetric bool) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if !symmetric {
params.KeySym = nil
params.Dst = &key.PublicKey
}
text := make([]byte, 0, 512)
text = append(text, params.Payload...)
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
var decrypted *ReceivedMessage
if symmetric {
decrypted, err = env.OpenSymmetric(params.KeySym)
} else {
decrypted, err = env.OpenAsymmetric(key)
}
if err != nil {
t.Fatalf("failed to encrypt with seed %d: %s.", seed, err)
}
if !decrypted.Validate() {
t.Fatalf("failed to validate with seed %d.", seed)
}
if !bytes.Equal(text, decrypted.Payload) {
t.Fatalf("failed with seed %d: compare payload.", seed)
}
if !isMessageSigned(decrypted.Raw[0]) {
t.Fatalf("failed with seed %d: unsigned.", seed)
}
if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
}
if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed)
}
}
func TestMessageEncryption(t *testing.T) {
InitSingleTest()
var symmetric bool
for i := 0; i < 256; i++ {
singleMessageTest(t, symmetric)
symmetric = !symmetric
}
}
func TestMessageWrap(t *testing.T) {
seed = int64(1777444222)
mrand.Seed(seed)
target := 128.0
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.TTL = 1
params.WorkTime = 12
params.PoW = target
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
pow := env.PoW()
if pow < target {
t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
}
// set PoW target too high, expect error
msg2, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.TTL = 1000000
params.WorkTime = 1
params.PoW = 10000000.0
_, err = msg2.Wrap(params)
if err == nil {
t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed)
}
}
func TestMessageSeal(t *testing.T) {
// this test depends on deterministic choice of seed (1976726903)
seed = int64(1976726903)
mrand.Seed(seed)
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.TTL = 1
aesnonce := make([]byte, 12)
mrand.Read(aesnonce)
env := NewEnvelope(params.TTL, params.Topic, aesnonce, msg)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
env.Expiry = uint32(seed) // make it deterministic
target := 32.0
params.WorkTime = 4
params.PoW = target
env.Seal(params)
env.calculatePoW(0)
pow := env.PoW()
if pow < target {
t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
}
params.WorkTime = 1
params.PoW = 1000000000.0
env.Seal(params)
env.calculatePoW(0)
pow = env.PoW()
if pow < 2*target {
t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow)
}
}
func TestEnvelopeOpen(t *testing.T) {
InitSingleTest()
var symmetric bool
for i := 0; i < 256; i++ {
singleEnvelopeOpenTest(t, symmetric)
symmetric = !symmetric
}
}
func singleEnvelopeOpenTest(t *testing.T, symmetric bool) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if !symmetric {
params.KeySym = nil
params.Dst = &key.PublicKey
}
text := make([]byte, 0, 512)
text = append(text, params.Payload...)
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key, KeySym: params.KeySym}
decrypted := env.Open(&f)
if decrypted == nil {
t.Fatalf("failed to open with seed %d.", seed)
}
if !bytes.Equal(text, decrypted.Payload) {
t.Fatalf("failed with seed %d: compare payload.", seed)
}
if !isMessageSigned(decrypted.Raw[0]) {
t.Fatalf("failed with seed %d: unsigned.", seed)
}
if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
}
if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed)
}
if decrypted.isAsymmetricEncryption() == symmetric {
t.Fatalf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric)
}
if decrypted.isSymmetricEncryption() != symmetric {
t.Fatalf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric)
}
if !symmetric {
if decrypted.Dst == nil {
t.Fatalf("failed with seed %d: dst is nil.", seed)
}
if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) {
t.Fatalf("failed with seed %d: Dst.", seed)
}
}
}
func TestEncryptWithZeroKey(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.KeySym = make([]byte, aesKeyLength)
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}
params, err = generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err = NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.KeySym = make([]byte, 0)
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with empty key, seed: %d.", seed)
}
params, err = generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err = NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.KeySym = nil
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with nil key, seed: %d.", seed)
}
}
func TestRlpEncode(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}
raw, err := rlp.EncodeToBytes(env)
if err != nil {
t.Fatalf("RLP encode failed: %s.", err)
}
var decoded Envelope
rlp.DecodeBytes(raw, &decoded)
if err != nil {
t.Fatalf("RLP decode failed: %s.", err)
}
he := env.Hash()
hd := decoded.Hash()
if he != hd {
t.Fatalf("Hashes are not equal: %x vs. %x", he, hd)
}
}
func singlePaddingTest(t *testing.T, padSize int) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err)
}
params.Padding = make([]byte, padSize)
params.PoW = 0.0000000001
pad := make([]byte, padSize)
_, err = mrand.Read(pad)
if err != nil {
t.Fatalf("padding is not generated (seed %d): %s", seed, err)
}
n := copy(params.Padding, pad)
if n != padSize {
t.Fatalf("padding is not copied (seed %d): %s", seed, err)
}
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize)
}
f := Filter{KeySym: params.KeySym}
decrypted := env.Open(&f)
if decrypted == nil {
t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize)
}
if !bytes.Equal(pad, decrypted.Padding) {
t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding)
}
}
func TestPadding(t *testing.T) {
InitSingleTest()
for i := 1; i < 260; i++ {
singlePaddingTest(t, i)
}
lim := 256 * 256
for i := lim - 5; i < lim+2; i++ {
singlePaddingTest(t, i)
}
for i := 0; i < 256; i++ {
n := mrand.Intn(256*254) + 256
singlePaddingTest(t, n)
}
for i := 0; i < 256; i++ {
n := mrand.Intn(256*1024) + 256*256
singlePaddingTest(t, n)
}
}

174
whisper/whisperv6/peer.go Normal file
View File

@ -0,0 +1,174 @@
// 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"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
set "gopkg.in/fatih/set.v0"
)
// peer represents a whisper protocol peer connection.
type Peer struct {
host *Whisper
peer *p2p.Peer
ws p2p.MsgReadWriter
trusted bool
known *set.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,
known: set.New(),
quit: make(chan struct{}),
}
}
// start initiates the peer updater, periodically broadcasting the whisper packets
// into the network.
func (p *Peer) start() {
go p.update()
log.Trace("start", "peer", p.ID())
}
// stop terminates the peer updater, stopping message forwarding to it.
func (p *Peer) stop() {
close(p.quit)
log.Trace("stop", "peer", p.ID())
}
// handshake sends the protocol initiation status message to the remote peer and
// verifies the remote status too.
func (p *Peer) handshake() error {
// Send the handshake status message asynchronously
errc := make(chan error, 1)
go func() {
errc <- p2p.Send(p.ws, statusCode, ProtocolVersion)
}()
// Fetch the remote status packet and verify protocol match
packet, err := p.ws.ReadMsg()
if err != nil {
return err
}
if packet.Code != statusCode {
return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code)
}
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
peerVersion, err := s.Uint()
if err != nil {
return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err)
}
if peerVersion != ProtocolVersion {
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion)
}
// Wait until out own status is consumed too
if err := <-errc; err != nil {
return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err)
}
return nil
}
// update executes periodic operations on the peer, including message transmission
// and expiration.
func (p *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:
p.expire()
case <-transmit.C:
if err := p.broadcast(); err != nil {
log.Trace("broadcast failed", "reason", err, "peer", p.ID())
return
}
case <-p.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.Has(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 (p *Peer) broadcast() error {
var cnt int
envelopes := p.host.Envelopes()
for _, envelope := range envelopes {
if !p.marked(envelope) {
err := p2p.Send(p.ws, messagesCode, envelope)
if err != nil {
return err
} else {
p.mark(envelope)
cnt++
}
}
}
if cnt > 0 {
log.Trace("broadcast", "num. messages", cnt)
}
return nil
}
func (p *Peer) ID() []byte {
id := p.peer.ID()
return id[:]
}

View File

@ -0,0 +1,306 @@
// 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 (
"bytes"
"crypto/ecdsa"
"fmt"
"net"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat"
)
var keys []string = []string{
"d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9",
"73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98",
"119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc",
"deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837",
"5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf",
"1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5",
"15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68",
"51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c",
"ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0",
"09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9",
"15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4",
"2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620",
"73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590",
"1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1",
"8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a",
"0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f",
"8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8",
"acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805",
"a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045",
"28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5",
"c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7",
"46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd",
"c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f",
"783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211",
"9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611",
"e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f",
"487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b",
"824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5",
"ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15",
"30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c",
"de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2",
"92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a",
"7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4",
}
const NumNodes = 16 // must not exceed the number of keys (32)
type TestData struct {
counter [NumNodes]int
mutex sync.RWMutex
}
type TestNode struct {
shh *Whisper
id *ecdsa.PrivateKey
server *p2p.Server
filerId string
}
var result TestData
var nodes [NumNodes]*TestNode
var sharedKey []byte = []byte("some arbitrary data here")
var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0}
var expectedMessage []byte = []byte("per rectum ad astra")
// This test does the following:
// 1. creates a chain of whisper nodes,
// 2. installs the filters with shared (predefined) parameters,
// 3. each node sends a number of random (undecryptable) messages,
// 4. first node sends one expected (decryptable) message,
// 5. checks if each node have received and decrypted exactly one message.
func TestSimulation(t *testing.T) {
initialize(t)
for i := 0; i < NumNodes; i++ {
sendMsg(t, false, i)
}
sendMsg(t, true, 0)
checkPropagation(t)
stopServers()
}
func initialize(t *testing.T) {
var err error
ip := net.IPv4(127, 0, 0, 1)
port0 := 30303
for i := 0; i < NumNodes; i++ {
var node TestNode
node.shh = New(&DefaultConfig)
node.shh.SetMinimumPoW(0.00000001)
node.shh.Start(nil)
topics := make([]TopicType, 0)
topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey}
f.Topics = [][]byte{topics[0][:]}
node.filerId, err = node.shh.Subscribe(&f)
if err != nil {
t.Fatalf("failed to install the filter: %s.", err)
}
node.id, err = crypto.HexToECDSA(keys[i])
if err != nil {
t.Fatalf("failed convert the key: %s.", keys[i])
}
port := port0 + i
addr := fmt.Sprintf(":%d", port) // e.g. ":30303"
name := common.MakeName("whisper-go", "2.0")
var peers []*discover.Node
if i > 0 {
peerNodeId := nodes[i-1].id
peerPort := uint16(port - 1)
peerNode := discover.PubkeyID(&peerNodeId.PublicKey)
peer := discover.NewNode(peerNode, ip, peerPort, peerPort)
peers = append(peers, peer)
}
node.server = &p2p.Server{
Config: p2p.Config{
PrivateKey: node.id,
MaxPeers: NumNodes/2 + 1,
Name: name,
Protocols: node.shh.Protocols(),
ListenAddr: addr,
NAT: nat.Any(),
BootstrapNodes: peers,
StaticNodes: peers,
TrustedNodes: peers,
},
}
err = node.server.Start()
if err != nil {
t.Fatalf("failed to start server %d.", i)
}
nodes[i] = &node
}
}
func stopServers() {
for i := 0; i < NumNodes; i++ {
n := nodes[i]
if n != nil {
n.shh.Unsubscribe(n.filerId)
n.shh.Stop()
n.server.Stop()
}
}
}
func checkPropagation(t *testing.T) {
if t.Failed() {
return
}
const cycle = 100
const iterations = 100
for j := 0; j < iterations; j++ {
time.Sleep(cycle * time.Millisecond)
for i := 0; i < NumNodes; i++ {
f := nodes[i].shh.GetFilter(nodes[i].filerId)
if f == nil {
t.Fatalf("failed to get filterId %s from node %d.", nodes[i].filerId, i)
}
mail := f.Retrieve()
if !validateMail(t, i, mail) {
return
}
if isTestComplete() {
return
}
}
}
t.Fatalf("Test was not complete: timeout %d seconds.", iterations*cycle/1000)
}
func validateMail(t *testing.T, index int, mail []*ReceivedMessage) bool {
var cnt int
for _, m := range mail {
if bytes.Equal(m.Payload, expectedMessage) {
cnt++
}
}
if cnt == 0 {
// no messages received yet: nothing is wrong
return true
}
if cnt > 1 {
t.Fatalf("node %d received %d.", index, cnt)
return false
}
if cnt > 0 {
result.mutex.Lock()
defer result.mutex.Unlock()
result.counter[index] += cnt
if result.counter[index] > 1 {
t.Fatalf("node %d accumulated %d.", index, result.counter[index])
}
}
return true
}
func isTestComplete() bool {
result.mutex.RLock()
defer result.mutex.RUnlock()
for i := 0; i < NumNodes; i++ {
if result.counter[i] < 1 {
return false
}
}
for i := 0; i < NumNodes; i++ {
envelopes := nodes[i].shh.Envelopes()
if len(envelopes) < 2 {
return false
}
}
return true
}
func sendMsg(t *testing.T, expected bool, id int) {
if t.Failed() {
return
}
opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1}
if !expected {
opt.KeySym[0]++
opt.Topic[0]++
opt.Payload = opt.Payload[1:]
}
msg, err := NewSentMessage(&opt)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
envelope, err := msg.Wrap(&opt)
if err != nil {
t.Fatalf("failed to seal message: %s", err)
}
err = nodes[id].shh.Send(envelope)
if err != nil {
t.Fatalf("failed to send message: %s", err)
}
}
func TestPeerBasic(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d.", seed)
}
params.PoW = 0.001
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d.", seed)
}
p := newPeer(nil, nil, nil)
p.mark(env)
if !p.marked(env) {
t.Fatalf("failed mark with seed %d.", seed)
}
}

View File

@ -0,0 +1,55 @@
// 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"
)
// Topic 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
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 (topic *TopicType) String() string {
return string(common.ToHex(topic[:]))
}
// 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[:])
}

View File

@ -0,0 +1,134 @@
// 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 (
"encoding/json"
"testing"
)
var topicStringTests = []struct {
topic TopicType
str string
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"},
}
func TestTopicString(t *testing.T) {
for i, tst := range topicStringTests {
s := tst.topic.String()
if s != tst.str {
t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str)
}
}
}
var bytesToTopicTests = []struct {
data []byte
topic TopicType
}{
{topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}},
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}},
{topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}},
{topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}},
{topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil},
}
var unmarshalTestsGood = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)},
}
var unmarshalTestsBad = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)},
}
var unmarshalTestsUgly = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)},
}
func TestBytesToTopic(t *testing.T) {
for i, tst := range bytesToTopicTests {
top := BytesToTopic(tst.data)
if top != tst.topic {
t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsGood(t *testing.T) {
for i, tst := range unmarshalTestsGood {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err)
} else if top != tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsBad(t *testing.T) {
// in this test UnmarshalJSON() is supposed to fail
for i, tst := range unmarshalTestsBad {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err == nil {
t.Fatalf("failed test %d. input: %v.", i, tst.data)
}
}
}
func TestUnmarshalTestsUgly(t *testing.T) {
// in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong
for i, tst := range unmarshalTestsUgly {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v.", i, tst.data)
} else if top == tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic)
}
}
}

View File

@ -0,0 +1,858 @@
// 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 (
"bytes"
"crypto/ecdsa"
crand "crypto/rand"
"crypto/sha256"
"fmt"
"runtime"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/syndtr/goleveldb/leveldb/errors"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/sync/syncmap"
set "gopkg.in/fatih/set.v0"
)
type Statistics struct {
messagesCleared int
memoryCleared int
memoryUsed int
cycles int
totalMessagesCleared int
}
const (
minPowIdx = iota // Minimal PoW required by the whisper node
maxMsgSizeIdx = iota // Maximal message length allowed by the whisper node
overflowIdx = iota // Indicator of message queue overflow
)
// Whisper represents a dark communication interface through the Ethereum
// network, using its very own P2P communication layer.
type Whisper struct {
protocol p2p.Protocol // Protocol description and parameters
filters *Filters // Message filters installed with Subscribe function
privateKeys map[string]*ecdsa.PrivateKey // Private key storage
symKeys map[string][]byte // Symmetric key storage
keyMu sync.RWMutex // Mutex associated with key storages
poolMu sync.RWMutex // Mutex to sync the message and expiration pools
envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node
expirations map[uint32]*set.SetNonTS // Message expiration pool
peerMu sync.RWMutex // Mutex to sync the active peer set
peers map[*Peer]struct{} // Set of currently active peers
messageQueue chan *Envelope // Message queue for normal whisper messages
p2pMsgQueue chan *Envelope // Message queue for peer-to-peer messages (not to be forwarded any further)
quit chan struct{} // Channel used for graceful exit
settings syncmap.Map // holds configuration settings that can be dynamically changed
statsMu sync.Mutex // guard stats
stats Statistics // Statistics of whisper node
mailServer MailServer // MailServer interface
}
// New creates a Whisper client ready to communicate through the Ethereum P2P network.
func New(cfg *Config) *Whisper {
if cfg == nil {
cfg = &DefaultConfig
}
whisper := &Whisper{
privateKeys: make(map[string]*ecdsa.PrivateKey),
symKeys: make(map[string][]byte),
envelopes: make(map[common.Hash]*Envelope),
expirations: make(map[uint32]*set.SetNonTS),
peers: make(map[*Peer]struct{}),
messageQueue: make(chan *Envelope, messageQueueLimit),
p2pMsgQueue: make(chan *Envelope, messageQueueLimit),
quit: make(chan struct{}),
}
whisper.filters = NewFilters(whisper)
whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW)
whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize)
whisper.settings.Store(overflowIdx, false)
// p2p whisper sub protocol handler
whisper.protocol = p2p.Protocol{
Name: ProtocolName,
Version: uint(ProtocolVersion),
Length: NumberOfMessageCodes,
Run: whisper.HandlePeer,
NodeInfo: func() interface{} {
return map[string]interface{}{
"version": ProtocolVersionStr,
"maxMessageSize": whisper.MaxMessageSize(),
"minimumPoW": whisper.MinPow(),
}
},
}
return whisper
}
func (w *Whisper) MinPow() float64 {
val, _ := w.settings.Load(minPowIdx)
return val.(float64)
}
// MaxMessageSize returns the maximum accepted message size.
func (w *Whisper) MaxMessageSize() uint32 {
val, _ := w.settings.Load(maxMsgSizeIdx)
return val.(uint32)
}
// Overflow returns an indication if the message queue is full.
func (w *Whisper) Overflow() bool {
val, _ := w.settings.Load(overflowIdx)
return val.(bool)
}
// APIs returns the RPC descriptors the Whisper implementation offers
func (w *Whisper) APIs() []rpc.API {
return []rpc.API{
{
Namespace: ProtocolName,
Version: ProtocolVersionStr,
Service: NewPublicWhisperAPI(w),
Public: true,
},
}
}
// RegisterServer registers MailServer interface.
// MailServer will process all the incoming messages with p2pRequestCode.
func (w *Whisper) RegisterServer(server MailServer) {
w.mailServer = server
}
// Protocols returns the whisper sub-protocols ran by this particular client.
func (w *Whisper) Protocols() []p2p.Protocol {
return []p2p.Protocol{w.protocol}
}
// Version returns the whisper sub-protocols version number.
func (w *Whisper) Version() uint {
return w.protocol.Version
}
// SetMaxMessageSize sets the maximal message size allowed by this node
func (w *Whisper) SetMaxMessageSize(size uint32) error {
if size > MaxMessageSize {
return fmt.Errorf("message size too large [%d>%d]", size, MaxMessageSize)
}
w.settings.Store(maxMsgSizeIdx, uint32(size))
return nil
}
// SetMinimumPoW sets the minimal PoW required by this node
func (w *Whisper) SetMinimumPoW(val float64) error {
if val <= 0.0 {
return fmt.Errorf("invalid PoW: %f", val)
}
w.settings.Store(minPowIdx, val)
return nil
}
// getPeer retrieves peer by ID
func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
w.peerMu.Lock()
defer w.peerMu.Unlock()
for p := range w.peers {
id := p.peer.ID()
if bytes.Equal(peerID, id[:]) {
return p, nil
}
}
return nil, fmt.Errorf("Could not find peer with ID: %x", peerID)
}
// AllowP2PMessagesFromPeer marks specific peer trusted,
// which will allow it to send historic (expired) messages.
func (w *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error {
p, err := w.getPeer(peerID)
if err != nil {
return err
}
p.trusted = true
return nil
}
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
// which is known to implement MailServer interface, and is supposed to process this
// request and respond with a number of peer-to-peer messages (possibly expired),
// which are not supposed to be forwarded any further.
// The whisper protocol is agnostic of the format and contents of envelope.
func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error {
p, err := w.getPeer(peerID)
if err != nil {
return err
}
p.trusted = true
return p2p.Send(p.ws, p2pRequestCode, envelope)
}
// SendP2PMessage sends a peer-to-peer message to a specific peer.
func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
p, err := w.getPeer(peerID)
if err != nil {
return err
}
return w.SendP2PDirect(p, envelope)
}
// SendP2PDirect sends a peer-to-peer message to a specific peer.
func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
return p2p.Send(peer.ws, p2pCode, envelope)
}
// NewKeyPair generates a new cryptographic identity for the client, and injects
// it into the known identities for message decryption. Returns ID of the new key pair.
func (w *Whisper) NewKeyPair() (string, error) {
key, err := crypto.GenerateKey()
if err != nil || !validatePrivateKey(key) {
key, err = crypto.GenerateKey() // retry once
}
if err != nil {
return "", err
}
if !validatePrivateKey(key) {
return "", fmt.Errorf("failed to generate valid key")
}
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("failed to generate ID: %s", err)
}
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.privateKeys[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
w.privateKeys[id] = key
return id, nil
}
// DeleteKeyPair deletes the specified key if it exists.
func (w *Whisper) DeleteKeyPair(key string) bool {
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.privateKeys[key] != nil {
delete(w.privateKeys, key)
return true
}
return false
}
// AddKeyPair imports a asymmetric private key and returns it identifier.
func (w *Whisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("failed to generate ID: %s", err)
}
w.keyMu.Lock()
w.privateKeys[id] = key
w.keyMu.Unlock()
return id, nil
}
// HasKeyPair checks if the the whisper node is configured with the private key
// of the specified public pair.
func (w *Whisper) HasKeyPair(id string) bool {
w.keyMu.RLock()
defer w.keyMu.RUnlock()
return w.privateKeys[id] != nil
}
// GetPrivateKey retrieves the private key of the specified identity.
func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
w.keyMu.RLock()
defer w.keyMu.RUnlock()
key := w.privateKeys[id]
if key == nil {
return nil, fmt.Errorf("invalid id")
}
return key, nil
}
// GenerateSymKey generates a random symmetric key and stores it under id,
// which is then returned. Will be used in the future for session key exchange.
func (w *Whisper) GenerateSymKey() (string, error) {
key := make([]byte, aesKeyLength)
_, err := crand.Read(key)
if err != nil {
return "", err
} else if !validateSymmetricKey(key) {
return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data")
}
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("failed to generate ID: %s", err)
}
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.symKeys[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
w.symKeys[id] = key
return id, nil
}
// AddSymKeyDirect stores the key, and returns its id.
func (w *Whisper) AddSymKeyDirect(key []byte) (string, error) {
if len(key) != aesKeyLength {
return "", fmt.Errorf("wrong key size: %d", len(key))
}
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("failed to generate ID: %s", err)
}
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.symKeys[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
w.symKeys[id] = key
return id, nil
}
// AddSymKeyFromPassword generates the key from password, stores it, and returns its id.
func (w *Whisper) AddSymKeyFromPassword(password string) (string, error) {
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("failed to generate ID: %s", err)
}
if w.HasSymKey(id) {
return "", fmt.Errorf("failed to generate unique ID")
}
derived, err := deriveKeyMaterial([]byte(password), EnvelopeVersion)
if err != nil {
return "", err
}
w.keyMu.Lock()
defer w.keyMu.Unlock()
// double check is necessary, because deriveKeyMaterial() is very slow
if w.symKeys[id] != nil {
return "", fmt.Errorf("critical error: failed to generate unique ID")
}
w.symKeys[id] = derived
return id, nil
}
// HasSymKey returns true if there is a key associated with the given id.
// Otherwise returns false.
func (w *Whisper) HasSymKey(id string) bool {
w.keyMu.RLock()
defer w.keyMu.RUnlock()
return w.symKeys[id] != nil
}
// DeleteSymKey deletes the key associated with the name string if it exists.
func (w *Whisper) DeleteSymKey(id string) bool {
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.symKeys[id] != nil {
delete(w.symKeys, id)
return true
}
return false
}
// GetSymKey returns the symmetric key associated with the given id.
func (w *Whisper) GetSymKey(id string) ([]byte, error) {
w.keyMu.RLock()
defer w.keyMu.RUnlock()
if w.symKeys[id] != nil {
return w.symKeys[id], nil
}
return nil, fmt.Errorf("non-existent key ID")
}
// Subscribe installs a new message handler used for filtering, decrypting
// and subsequent storing of incoming messages.
func (w *Whisper) Subscribe(f *Filter) (string, error) {
return w.filters.Install(f)
}
// GetFilter returns the filter by id.
func (w *Whisper) GetFilter(id string) *Filter {
return w.filters.Get(id)
}
// Unsubscribe removes an installed message handler.
func (w *Whisper) Unsubscribe(id string) error {
ok := w.filters.Uninstall(id)
if !ok {
return fmt.Errorf("Unsubscribe: Invalid ID")
}
return nil
}
// Send injects a message into the whisper send queue, to be distributed in the
// network in the coming cycles.
func (w *Whisper) Send(envelope *Envelope) error {
ok, err := w.add(envelope)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("failed to add envelope")
}
return err
}
// Start implements node.Service, starting the background data propagation thread
// of the Whisper protocol.
func (w *Whisper) Start(*p2p.Server) error {
log.Info("started whisper v." + ProtocolVersionStr)
go w.update()
numCPU := runtime.NumCPU()
for i := 0; i < numCPU; i++ {
go w.processQueue()
}
return nil
}
// Stop implements node.Service, stopping the background data propagation thread
// of the Whisper protocol.
func (w *Whisper) Stop() error {
close(w.quit)
log.Info("whisper stopped")
return nil
}
// HandlePeer is called by the underlying P2P layer when the whisper sub-protocol
// connection is negotiated.
func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
// Create the new peer and start tracking it
whisperPeer := newPeer(wh, peer, rw)
wh.peerMu.Lock()
wh.peers[whisperPeer] = struct{}{}
wh.peerMu.Unlock()
defer func() {
wh.peerMu.Lock()
delete(wh.peers, whisperPeer)
wh.peerMu.Unlock()
}()
// Run the peer handshake and state updates
if err := whisperPeer.handshake(); err != nil {
return err
}
whisperPeer.start()
defer whisperPeer.stop()
return wh.runMessageLoop(whisperPeer, rw)
}
// runMessageLoop reads and processes inbound messages directly to merge into client-global state.
func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
for {
// fetch the next packet
packet, err := rw.ReadMsg()
if err != nil {
log.Warn("message loop", "peer", p.peer.ID(), "err", err)
return err
}
if packet.Size > wh.MaxMessageSize() {
log.Warn("oversized message received", "peer", p.peer.ID())
return errors.New("oversized message received")
}
switch packet.Code {
case statusCode:
// this should not happen, but no need to panic; just ignore this message.
log.Warn("unxepected status message received", "peer", p.peer.ID())
case messagesCode:
// decode the contained envelopes
var envelope Envelope
if err := packet.Decode(&envelope); err != nil {
log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return errors.New("invalid envelope")
}
cached, err := wh.add(&envelope)
if err != nil {
log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return errors.New("invalid envelope")
}
if cached {
p.mark(&envelope)
}
case p2pCode:
// peer-to-peer message, sent directly to peer bypassing PoW checks, etc.
// this message is not supposed to be forwarded to other peers, and
// therefore might not satisfy the PoW, expiry and other requirements.
// these messages are only accepted from the trusted peer.
if p.trusted {
var envelope Envelope
if err := packet.Decode(&envelope); err != nil {
log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return errors.New("invalid direct message")
}
wh.postEvent(&envelope, true)
}
case p2pRequestCode:
// Must be processed if mail server is implemented. Otherwise ignore.
if wh.mailServer != nil {
var request Envelope
if err := packet.Decode(&request); err != nil {
log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return errors.New("invalid p2p request")
}
wh.mailServer.DeliverMail(p, &request)
}
default:
// New message types might be implemented in the future versions of Whisper.
// For forward compatibility, just ignore.
}
packet.Discard()
}
}
// add inserts a new envelope into the message pool to be distributed within the
// whisper network. It also inserts the envelope into the expiration pool at the
// appropriate time-stamp. In case of error, connection should be dropped.
func (wh *Whisper) add(envelope *Envelope) (bool, error) {
now := uint32(time.Now().Unix())
sent := envelope.Expiry - envelope.TTL
if sent > now {
if sent-SynchAllowance > now {
return false, fmt.Errorf("envelope created in the future [%x]", envelope.Hash())
} else {
// recalculate PoW, adjusted for the time difference, plus one second for latency
envelope.calculatePoW(sent - now + 1)
}
}
if envelope.Expiry < now {
if envelope.Expiry+SynchAllowance*2 < now {
return false, fmt.Errorf("very old message")
} else {
log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex())
return false, nil // drop envelope without error
}
}
if uint32(envelope.size()) > wh.MaxMessageSize() {
return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash())
}
if len(envelope.Version) > 4 {
return false, fmt.Errorf("oversized version [%x]", envelope.Hash())
}
aesNonceSize := len(envelope.AESNonce)
if aesNonceSize != 0 && aesNonceSize != AESNonceLength {
// the standard AES GCM nonce size is 12 bytes,
// but constant gcmStandardNonceSize cannot be accessed (not exported)
return false, fmt.Errorf("wrong size of AESNonce: %d bytes [env: %x]", aesNonceSize, envelope.Hash())
}
if envelope.PoW() < wh.MinPow() {
log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex())
return false, nil // drop envelope without error
}
hash := envelope.Hash()
wh.poolMu.Lock()
_, alreadyCached := wh.envelopes[hash]
if !alreadyCached {
wh.envelopes[hash] = envelope
if wh.expirations[envelope.Expiry] == nil {
wh.expirations[envelope.Expiry] = set.NewNonTS()
}
if !wh.expirations[envelope.Expiry].Has(hash) {
wh.expirations[envelope.Expiry].Add(hash)
}
}
wh.poolMu.Unlock()
if alreadyCached {
log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex())
} else {
log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex())
wh.statsMu.Lock()
wh.stats.memoryUsed += envelope.size()
wh.statsMu.Unlock()
wh.postEvent(envelope, false) // notify the local node about the new message
if wh.mailServer != nil {
wh.mailServer.Archive(envelope)
}
}
return true, nil
}
// postEvent queues the message for further processing.
func (w *Whisper) postEvent(envelope *Envelope, isP2P bool) {
// if the version of incoming message is higher than
// currently supported version, we can not decrypt it,
// and therefore just ignore this message
if envelope.Ver() <= EnvelopeVersion {
if isP2P {
w.p2pMsgQueue <- envelope
} else {
w.checkOverflow()
w.messageQueue <- envelope
}
}
}
// checkOverflow checks if message queue overflow occurs and reports it if necessary.
func (w *Whisper) checkOverflow() {
queueSize := len(w.messageQueue)
if queueSize == messageQueueLimit {
if !w.Overflow() {
w.settings.Store(overflowIdx, true)
log.Warn("message queue overflow")
}
} else if queueSize <= messageQueueLimit/2 {
if w.Overflow() {
w.settings.Store(overflowIdx, false)
log.Warn("message queue overflow fixed (back to normal)")
}
}
}
// processQueue delivers the messages to the watchers during the lifetime of the whisper node.
func (w *Whisper) processQueue() {
var e *Envelope
for {
select {
case <-w.quit:
return
case e = <-w.messageQueue:
w.filters.NotifyWatchers(e, false)
case e = <-w.p2pMsgQueue:
w.filters.NotifyWatchers(e, true)
}
}
}
// update loops until the lifetime of the whisper node, updating its internal
// state by expiring stale messages from the pool.
func (w *Whisper) update() {
// Start a ticker to check for expirations
expire := time.NewTicker(expirationCycle)
// Repeat updates until termination is requested
for {
select {
case <-expire.C:
w.expire()
case <-w.quit:
return
}
}
}
// expire iterates over all the expiration timestamps, removing all stale
// messages from the pools.
func (w *Whisper) expire() {
w.poolMu.Lock()
defer w.poolMu.Unlock()
w.statsMu.Lock()
defer w.statsMu.Unlock()
w.stats.reset()
now := uint32(time.Now().Unix())
for expiry, hashSet := range w.expirations {
if expiry < now {
// Dump all expired messages and remove timestamp
hashSet.Each(func(v interface{}) bool {
sz := w.envelopes[v.(common.Hash)].size()
delete(w.envelopes, v.(common.Hash))
w.stats.messagesCleared++
w.stats.memoryCleared += sz
w.stats.memoryUsed -= sz
return true
})
w.expirations[expiry].Clear()
delete(w.expirations, expiry)
}
}
}
// Stats returns the whisper node statistics.
func (w *Whisper) Stats() Statistics {
w.statsMu.Lock()
defer w.statsMu.Unlock()
return w.stats
}
// Envelopes retrieves all the messages currently pooled by the node.
func (w *Whisper) Envelopes() []*Envelope {
w.poolMu.RLock()
defer w.poolMu.RUnlock()
all := make([]*Envelope, 0, len(w.envelopes))
for _, envelope := range w.envelopes {
all = append(all, envelope)
}
return all
}
// Messages iterates through all currently floating envelopes
// and retrieves all the messages, that this filter could decrypt.
func (w *Whisper) Messages(id string) []*ReceivedMessage {
result := make([]*ReceivedMessage, 0)
w.poolMu.RLock()
defer w.poolMu.RUnlock()
if filter := w.filters.Get(id); filter != nil {
for _, env := range w.envelopes {
msg := filter.processEnvelope(env)
if msg != nil {
result = append(result, msg)
}
}
}
return result
}
// isEnvelopeCached checks if envelope with specific hash has already been received and cached.
func (w *Whisper) isEnvelopeCached(hash common.Hash) bool {
w.poolMu.Lock()
defer w.poolMu.Unlock()
_, exist := w.envelopes[hash]
return exist
}
// reset resets the node's statistics after each expiry cycle.
func (s *Statistics) reset() {
s.cycles++
s.totalMessagesCleared += s.messagesCleared
s.memoryCleared = 0
s.messagesCleared = 0
}
// ValidatePublicKey checks the format of the given public key.
func ValidatePublicKey(k *ecdsa.PublicKey) bool {
return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
}
// validatePrivateKey checks the format of the given private key.
func validatePrivateKey(k *ecdsa.PrivateKey) bool {
if k == nil || k.D == nil || k.D.Sign() == 0 {
return false
}
return ValidatePublicKey(&k.PublicKey)
}
// validateSymmetricKey returns false if the key contains all zeros
func validateSymmetricKey(k []byte) bool {
return len(k) > 0 && !containsOnlyZeros(k)
}
// containsOnlyZeros checks if the data contain only zeros.
func containsOnlyZeros(data []byte) bool {
for _, b := range data {
if b != 0 {
return false
}
}
return true
}
// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer.
func bytesToUintLittleEndian(b []byte) (res uint64) {
mul := uint64(1)
for i := 0; i < len(b); i++ {
res += uint64(b[i]) * mul
mul *= 256
}
return res
}
// BytesToUintBigEndian converts the slice to 64-bit unsigned integer.
func BytesToUintBigEndian(b []byte) (res uint64) {
for i := 0; i < len(b); i++ {
res *= 256
res += uint64(b[i])
}
return res
}
// deriveKeyMaterial derives symmetric key material from the key or password.
// pbkdf2 is used for security, in case people use password instead of randomly generated keys.
func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error) {
if version == 0 {
// kdf should run no less than 0.1 seconds on average compute,
// because it's a once in a session experience
derivedKey := pbkdf2.Key(key, nil, 65356, aesKeyLength, sha256.New)
return derivedKey, nil
} else {
return nil, unknownVersionError(version)
}
}
// GenerateRandomID generates a random string, which is then returned to be used as a key id
func GenerateRandomID() (id string, err error) {
buf := make([]byte, keyIdSize)
_, err = crand.Read(buf)
if err != nil {
return "", err
}
if !validateSymmetricKey(buf) {
return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
}
id = common.Bytes2Hex(buf)
return id, err
}

View File

@ -0,0 +1,851 @@
// 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 (
"bytes"
"crypto/ecdsa"
mrand "math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
)
func TestWhisperBasic(t *testing.T) {
w := New(&DefaultConfig)
p := w.Protocols()
shh := p[0]
if shh.Name != ProtocolName {
t.Fatalf("failed Protocol Name: %v.", shh.Name)
}
if uint64(shh.Version) != ProtocolVersion {
t.Fatalf("failed Protocol Version: %v.", shh.Version)
}
if shh.Length != NumberOfMessageCodes {
t.Fatalf("failed Protocol Length: %v.", shh.Length)
}
if shh.Run == nil {
t.Fatalf("failed shh.Run.")
}
if uint64(w.Version()) != ProtocolVersion {
t.Fatalf("failed whisper Version: %v.", shh.Version)
}
if w.GetFilter("non-existent") != nil {
t.Fatalf("failed GetFilter.")
}
peerID := make([]byte, 64)
mrand.Read(peerID)
peer, _ := w.getPeer(peerID)
if peer != nil {
t.Fatal("found peer for random key.")
}
if err := w.AllowP2PMessagesFromPeer(peerID); err == nil {
t.Fatalf("failed MarkPeerTrusted.")
}
exist := w.HasSymKey("non-existing")
if exist {
t.Fatalf("failed HasSymKey.")
}
key, err := w.GetSymKey("non-existing")
if err == nil {
t.Fatalf("failed GetSymKey(non-existing): false positive.")
}
if key != nil {
t.Fatalf("failed GetSymKey: false positive.")
}
mail := w.Envelopes()
if len(mail) != 0 {
t.Fatalf("failed w.Envelopes().")
}
m := w.Messages("non-existent")
if len(m) != 0 {
t.Fatalf("failed w.Messages.")
}
var derived []byte
ver := uint64(0xDEADBEEF)
if _, err := deriveKeyMaterial(peerID, ver); err != unknownVersionError(ver) {
t.Fatalf("failed deriveKeyMaterial with param = %v: %s.", peerID, err)
}
derived, err = deriveKeyMaterial(peerID, 0)
if err != nil {
t.Fatalf("failed second deriveKeyMaterial with param = %v: %s.", peerID, err)
}
if !validateSymmetricKey(derived) {
t.Fatalf("failed validateSymmetricKey with param = %v.", derived)
}
if containsOnlyZeros(derived) {
t.Fatalf("failed containsOnlyZeros with param = %v.", derived)
}
buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0}
le := bytesToUintLittleEndian(buf)
be := BytesToUintBigEndian(buf)
if le != uint64(0x280e5ff) {
t.Fatalf("failed bytesToIntLittleEndian: %d.", le)
}
if be != uint64(0xffe5800200) {
t.Fatalf("failed BytesToIntBigEndian: %d.", be)
}
id, err := w.NewKeyPair()
if err != nil {
t.Fatalf("failed to generate new key pair: %s.", err)
}
pk, err := w.GetPrivateKey(id)
if err != nil {
t.Fatalf("failed to retrieve new key pair: %s.", err)
}
if !validatePrivateKey(pk) {
t.Fatalf("failed validatePrivateKey: %v.", pk)
}
if !ValidatePublicKey(&pk.PublicKey) {
t.Fatalf("failed ValidatePublicKey: %v.", pk)
}
}
func TestWhisperAsymmetricKeyImport(t *testing.T) {
var (
w = New(&DefaultConfig)
privateKeys []*ecdsa.PrivateKey
)
for i := 0; i < 50; i++ {
id, err := w.NewKeyPair()
if err != nil {
t.Fatalf("could not generate key: %v", err)
}
pk, err := w.GetPrivateKey(id)
if err != nil {
t.Fatalf("could not export private key: %v", err)
}
privateKeys = append(privateKeys, pk)
if !w.DeleteKeyPair(id) {
t.Fatalf("could not delete private key")
}
}
for _, pk := range privateKeys {
if _, err := w.AddKeyPair(pk); err != nil {
t.Fatalf("could not import private key: %v", err)
}
}
}
func TestWhisperIdentityManagement(t *testing.T) {
w := New(&DefaultConfig)
id1, err := w.NewKeyPair()
if err != nil {
t.Fatalf("failed to generate new key pair: %s.", err)
}
id2, err := w.NewKeyPair()
if err != nil {
t.Fatalf("failed to generate new key pair: %s.", err)
}
pk1, err := w.GetPrivateKey(id1)
if err != nil {
t.Fatalf("failed to retrieve the key pair: %s.", err)
}
pk2, err := w.GetPrivateKey(id2)
if err != nil {
t.Fatalf("failed to retrieve the key pair: %s.", err)
}
if !w.HasKeyPair(id1) {
t.Fatalf("failed HasIdentity(pk1).")
}
if !w.HasKeyPair(id2) {
t.Fatalf("failed HasIdentity(pk2).")
}
if pk1 == nil {
t.Fatalf("failed GetIdentity(pk1).")
}
if pk2 == nil {
t.Fatalf("failed GetIdentity(pk2).")
}
if !validatePrivateKey(pk1) {
t.Fatalf("pk1 is invalid.")
}
if !validatePrivateKey(pk2) {
t.Fatalf("pk2 is invalid.")
}
// Delete one identity
done := w.DeleteKeyPair(id1)
if !done {
t.Fatalf("failed to delete id1.")
}
pk1, err = w.GetPrivateKey(id1)
if err == nil {
t.Fatalf("retrieve the key pair: false positive.")
}
pk2, err = w.GetPrivateKey(id2)
if err != nil {
t.Fatalf("failed to retrieve the key pair: %s.", err)
}
if w.HasKeyPair(id1) {
t.Fatalf("failed DeleteIdentity(pub1): still exist.")
}
if !w.HasKeyPair(id2) {
t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.")
}
if pk1 != nil {
t.Fatalf("failed DeleteIdentity(pub1): first key still exist.")
}
if pk2 == nil {
t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.")
}
// Delete again non-existing identity
done = w.DeleteKeyPair(id1)
if done {
t.Fatalf("delete id1: false positive.")
}
pk1, err = w.GetPrivateKey(id1)
if err == nil {
t.Fatalf("retrieve the key pair: false positive.")
}
pk2, err = w.GetPrivateKey(id2)
if err != nil {
t.Fatalf("failed to retrieve the key pair: %s.", err)
}
if w.HasKeyPair(id1) {
t.Fatalf("failed delete non-existing identity: exist.")
}
if !w.HasKeyPair(id2) {
t.Fatalf("failed delete non-existing identity: pub2 does not exist.")
}
if pk1 != nil {
t.Fatalf("failed delete non-existing identity: first key exist.")
}
if pk2 == nil {
t.Fatalf("failed delete non-existing identity: second key does not exist.")
}
// Delete second identity
done = w.DeleteKeyPair(id2)
if !done {
t.Fatalf("failed to delete id2.")
}
pk1, err = w.GetPrivateKey(id1)
if err == nil {
t.Fatalf("retrieve the key pair: false positive.")
}
pk2, err = w.GetPrivateKey(id2)
if err == nil {
t.Fatalf("retrieve the key pair: false positive.")
}
if w.HasKeyPair(id1) {
t.Fatalf("failed delete second identity: first identity exist.")
}
if w.HasKeyPair(id2) {
t.Fatalf("failed delete second identity: still exist.")
}
if pk1 != nil {
t.Fatalf("failed delete second identity: first key exist.")
}
if pk2 != nil {
t.Fatalf("failed delete second identity: second key exist.")
}
}
func TestWhisperSymKeyManagement(t *testing.T) {
InitSingleTest()
var err error
var k1, k2 []byte
w := New(&DefaultConfig)
id1 := string("arbitrary-string-1")
id2 := string("arbitrary-string-2")
id1, err = w.GenerateSymKey()
if err != nil {
t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err)
}
k1, err = w.GetSymKey(id1)
if err != nil {
t.Fatalf("failed GetSymKey(id1).")
}
k2, err = w.GetSymKey(id2)
if err == nil {
t.Fatalf("failed GetSymKey(id2): false positive.")
}
if !w.HasSymKey(id1) {
t.Fatalf("failed HasSymKey(id1).")
}
if w.HasSymKey(id2) {
t.Fatalf("failed HasSymKey(id2): false positive.")
}
if k1 == nil {
t.Fatalf("first key does not exist.")
}
if k2 != nil {
t.Fatalf("second key still exist.")
}
// add existing id, nothing should change
randomKey := make([]byte, aesKeyLength)
mrand.Read(randomKey)
id1, err = w.AddSymKeyDirect(randomKey)
if err != nil {
t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err)
}
k1, err = w.GetSymKey(id1)
if err != nil {
t.Fatalf("failed w.GetSymKey(id1).")
}
k2, err = w.GetSymKey(id2)
if err == nil {
t.Fatalf("failed w.GetSymKey(id2): false positive.")
}
if !w.HasSymKey(id1) {
t.Fatalf("failed w.HasSymKey(id1).")
}
if w.HasSymKey(id2) {
t.Fatalf("failed w.HasSymKey(id2): false positive.")
}
if k1 == nil {
t.Fatalf("first key does not exist.")
}
if !bytes.Equal(k1, randomKey) {
t.Fatalf("k1 != randomKey.")
}
if k2 != nil {
t.Fatalf("second key already exist.")
}
id2, err = w.AddSymKeyDirect(randomKey)
if err != nil {
t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err)
}
k1, err = w.GetSymKey(id1)
if err != nil {
t.Fatalf("failed w.GetSymKey(id1).")
}
k2, err = w.GetSymKey(id2)
if err != nil {
t.Fatalf("failed w.GetSymKey(id2).")
}
if !w.HasSymKey(id1) {
t.Fatalf("HasSymKey(id1) failed.")
}
if !w.HasSymKey(id2) {
t.Fatalf("HasSymKey(id2) failed.")
}
if k1 == nil {
t.Fatalf("k1 does not exist.")
}
if k2 == nil {
t.Fatalf("k2 does not exist.")
}
if !bytes.Equal(k1, k2) {
t.Fatalf("k1 != k2.")
}
if !bytes.Equal(k1, randomKey) {
t.Fatalf("k1 != randomKey.")
}
if len(k1) != aesKeyLength {
t.Fatalf("wrong length of k1.")
}
if len(k2) != aesKeyLength {
t.Fatalf("wrong length of k2.")
}
w.DeleteSymKey(id1)
k1, err = w.GetSymKey(id1)
if err == nil {
t.Fatalf("failed w.GetSymKey(id1): false positive.")
}
if k1 != nil {
t.Fatalf("failed GetSymKey(id1): false positive.")
}
k2, err = w.GetSymKey(id2)
if err != nil {
t.Fatalf("failed w.GetSymKey(id2).")
}
if w.HasSymKey(id1) {
t.Fatalf("failed to delete first key: still exist.")
}
if !w.HasSymKey(id2) {
t.Fatalf("failed to delete first key: second key does not exist.")
}
if k1 != nil {
t.Fatalf("failed to delete first key.")
}
if k2 == nil {
t.Fatalf("failed to delete first key: second key is nil.")
}
w.DeleteSymKey(id1)
w.DeleteSymKey(id2)
k1, err = w.GetSymKey(id1)
if err == nil {
t.Fatalf("failed w.GetSymKey(id1): false positive.")
}
k2, err = w.GetSymKey(id2)
if err == nil {
t.Fatalf("failed w.GetSymKey(id2): false positive.")
}
if k1 != nil || k2 != nil {
t.Fatalf("k1 or k2 is not nil")
}
if w.HasSymKey(id1) {
t.Fatalf("failed to delete second key: first key exist.")
}
if w.HasSymKey(id2) {
t.Fatalf("failed to delete second key: still exist.")
}
if k1 != nil {
t.Fatalf("failed to delete second key: first key is not nil.")
}
if k2 != nil {
t.Fatalf("failed to delete second key: second key is not nil.")
}
randomKey = make([]byte, aesKeyLength+1)
mrand.Read(randomKey)
_, err = w.AddSymKeyDirect(randomKey)
if err == nil {
t.Fatalf("added the key with wrong size, seed %d.", seed)
}
const password = "arbitrary data here"
id1, err = w.AddSymKeyFromPassword(password)
if err != nil {
t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err)
}
id2, err = w.AddSymKeyFromPassword(password)
if err != nil {
t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err)
}
k1, err = w.GetSymKey(id1)
if err != nil {
t.Fatalf("failed w.GetSymKey(id1).")
}
k2, err = w.GetSymKey(id2)
if err != nil {
t.Fatalf("failed w.GetSymKey(id2).")
}
if !w.HasSymKey(id1) {
t.Fatalf("HasSymKey(id1) failed.")
}
if !w.HasSymKey(id2) {
t.Fatalf("HasSymKey(id2) failed.")
}
if k1 == nil {
t.Fatalf("k1 does not exist.")
}
if k2 == nil {
t.Fatalf("k2 does not exist.")
}
if !bytes.Equal(k1, k2) {
t.Fatalf("k1 != k2.")
}
if len(k1) != aesKeyLength {
t.Fatalf("wrong length of k1.")
}
if len(k2) != aesKeyLength {
t.Fatalf("wrong length of k2.")
}
if !validateSymmetricKey(k2) {
t.Fatalf("key validation failed.")
}
}
func TestExpiry(t *testing.T) {
InitSingleTest()
w := New(&DefaultConfig)
w.SetMinimumPoW(0.0000001)
defer w.SetMinimumPoW(DefaultMinimumPoW)
w.Start(nil)
defer w.Stop()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.TTL = 1
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err != nil {
t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
}
// wait till received or timeout
var received, expired bool
for j := 0; j < 20; j++ {
time.Sleep(100 * time.Millisecond)
if len(w.Envelopes()) > 0 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// wait till expired or timeout
for j := 0; j < 20; j++ {
time.Sleep(100 * time.Millisecond)
if len(w.Envelopes()) == 0 {
expired = true
break
}
}
if !expired {
t.Fatalf("expire failed, seed: %d.", seed)
}
}
func TestCustomization(t *testing.T) {
InitSingleTest()
w := New(&DefaultConfig)
defer w.SetMinimumPoW(DefaultMinimumPoW)
defer w.SetMaxMessageSize(DefaultMaxMessageSize)
w.Start(nil)
defer w.Stop()
const smallPoW = 0.00001
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.KeySym = f.KeySym
params.Topic = BytesToTopic(f.Topics[2])
params.PoW = smallPoW
params.TTL = 3600 * 24 // one day
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err == nil {
t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed)
}
w.SetMinimumPoW(smallPoW / 2)
err = w.Send(env)
if err != nil {
t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
}
params.TTL++
msg, err = NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err = msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
w.SetMaxMessageSize(uint32(env.size() - 1))
err = w.Send(env)
if err == nil {
t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed)
}
w.SetMaxMessageSize(DefaultMaxMessageSize)
err = w.Send(env)
if err != nil {
t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err)
}
// wait till received or timeout
var received bool
for j := 0; j < 20; j++ {
time.Sleep(100 * time.Millisecond)
if len(w.Envelopes()) > 1 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// check w.messages()
id, err := w.Subscribe(f)
if err != nil {
t.Fatalf("failed subscribe with seed %d: %s.", seed, err)
}
time.Sleep(5 * time.Millisecond)
mail := f.Retrieve()
if len(mail) > 0 {
t.Fatalf("received premature mail")
}
mail = w.Messages(id)
if len(mail) != 2 {
t.Fatalf("failed to get whisper messages")
}
}
func TestSymmetricSendCycle(t *testing.T) {
InitSingleTest()
w := New(&DefaultConfig)
defer w.SetMinimumPoW(DefaultMinimumPoW)
defer w.SetMaxMessageSize(DefaultMaxMessageSize)
w.Start(nil)
defer w.Stop()
filter1, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
filter1.PoW = DefaultMinimumPoW
// Copy the first filter since some of its fields
// are randomly gnerated.
filter2 := &Filter{
KeySym: filter1.KeySym,
Topics: filter1.Topics,
PoW: filter1.PoW,
AllowP2P: filter1.AllowP2P,
Messages: make(map[common.Hash]*ReceivedMessage),
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
filter1.Src = &params.Src.PublicKey
filter2.Src = &params.Src.PublicKey
params.KeySym = filter1.KeySym
params.Topic = BytesToTopic(filter1.Topics[2])
params.PoW = filter1.PoW
params.WorkTime = 10
params.TTL = 50
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
_, err = w.Subscribe(filter1)
if err != nil {
t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
}
_, err = w.Subscribe(filter2)
if err != nil {
t.Fatalf("failed subscribe 2 with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err != nil {
t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
}
// wait till received or timeout
var received bool
for j := 0; j < 200; j++ {
time.Sleep(10 * time.Millisecond)
if len(w.Envelopes()) > 0 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// check w.messages()
time.Sleep(5 * time.Millisecond)
mail1 := filter1.Retrieve()
mail2 := filter2.Retrieve()
if len(mail2) == 0 {
t.Fatalf("did not receive any email for filter 2")
}
if len(mail1) == 0 {
t.Fatalf("did not receive any email for filter 1")
}
}
func TestSymmetricSendWithoutAKey(t *testing.T) {
InitSingleTest()
w := New(&DefaultConfig)
defer w.SetMinimumPoW(DefaultMinimumPoW)
defer w.SetMaxMessageSize(DefaultMaxMessageSize)
w.Start(nil)
defer w.Stop()
filter, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
filter.PoW = DefaultMinimumPoW
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
filter.Src = nil
params.KeySym = filter.KeySym
params.Topic = BytesToTopic(filter.Topics[2])
params.PoW = filter.PoW
params.WorkTime = 10
params.TTL = 50
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
_, err = w.Subscribe(filter)
if err != nil {
t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err != nil {
t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
}
// wait till received or timeout
var received bool
for j := 0; j < 200; j++ {
time.Sleep(10 * time.Millisecond)
if len(w.Envelopes()) > 0 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// check w.messages()
time.Sleep(5 * time.Millisecond)
mail := filter.Retrieve()
if len(mail) == 0 {
t.Fatalf("did not receive message in spite of not setting a public key")
}
}
func TestSymmetricSendKeyMismatch(t *testing.T) {
InitSingleTest()
w := New(&DefaultConfig)
defer w.SetMinimumPoW(DefaultMinimumPoW)
defer w.SetMaxMessageSize(DefaultMaxMessageSize)
w.Start(nil)
defer w.Stop()
filter, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
filter.PoW = DefaultMinimumPoW
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.KeySym = filter.KeySym
params.Topic = BytesToTopic(filter.Topics[2])
params.PoW = filter.PoW
params.WorkTime = 10
params.TTL = 50
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
_, err = w.Subscribe(filter)
if err != nil {
t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err != nil {
t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
}
// wait till received or timeout
var received bool
for j := 0; j < 200; j++ {
time.Sleep(10 * time.Millisecond)
if len(w.Envelopes()) > 0 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// check w.messages()
time.Sleep(5 * time.Millisecond)
mail := filter.Retrieve()
if len(mail) > 0 {
t.Fatalf("received a message when keys weren't matching")
}
}