Skip to content
8 changes: 5 additions & 3 deletions whisper/whisperv6/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ import (

const (
EnvelopeVersion = uint64(0)
ProtocolVersion = uint64(5)
ProtocolVersionStr = "5.0"
ProtocolVersion = uint64(6)
ProtocolVersionStr = "6.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
NumberOfMessageCodes = 128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you make this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the EIP-627 packet codes specifies that:

The message codes reserved for Whisper protocol: 0 - 127. Messages with unknown codes must be ignored, for forward compatibility of future versions.

Support for messages with codes 126 and 127 will be added in subsequent PRs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change to EIP was made after discussion with Robert (Parity) several months ago.


paddingMask = byte(3)
signatureFlag = byte(4)
Expand All @@ -67,6 +67,8 @@ const (

DefaultTTL = 50 // seconds
SynchAllowance = 10 // seconds

EnvelopeHeaderLength = 20
)

type unknownVersionError uint64
Expand Down
52 changes: 24 additions & 28 deletions whisper/whisperv6/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ import (
// 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
Nonce uint64
Version []byte
Expiry uint32
TTL uint32
Topic TopicType
Data []byte
Nonce 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.
Expand All @@ -51,26 +50,25 @@ type Envelope struct {

// 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)
return EnvelopeHeaderLength + len(e.Version) + 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})
res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.Data})
return res
}

// NewEnvelope wraps a Whisper message with expiration and destination data
// included into an envelope for network forwarding.
func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage) *Envelope {
func NewEnvelope(ttl uint32, topic TopicType, 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,
Nonce: 0,
Version: make([]byte, 1),
Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
TTL: ttl,
Topic: topic,
Data: msg.Raw,
Nonce: 0,
}

if EnvelopeVersion < 256 {
Expand All @@ -82,14 +80,6 @@ func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage)
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)
}
Expand Down Expand Up @@ -209,7 +199,7 @@ func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, erro
// 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)
err = msg.decryptSymmetric(key)
if err != nil {
msg = nil
}
Expand All @@ -218,12 +208,18 @@ func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {

// 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() {
// The API interface forbids filters doing both symmetric and
// asymmetric encryption.
if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
return nil
}

if watcher.expectsAsymmetricEncryption() {
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
if msg != nil {
msg.Dst = &watcher.KeyAsym.PublicKey
}
} else if e.IsSymmetric() {
} else if watcher.expectsSymmetricEncryption() {
msg, _ = e.OpenSymmetric(watcher.KeySym)
if msg != nil {
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
Expand Down
64 changes: 64 additions & 0 deletions whisper/whisperv6/envelope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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/>.

// Contains the tests associated with the Whisper protocol Envelope object.

package whisperv6

import (
mrand "math/rand"
"testing"

"github.com/ethereum/go-ethereum/crypto"
)

func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) {
symKey := make([]byte, aesKeyLength)
mrand.Read(symKey)

asymKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}

params := MessageParams{
PoW: 0.01,
WorkTime: 1,
TTL: uint32(mrand.Intn(1024)),
Payload: make([]byte, 50),
KeySym: symKey,
Dst: nil,
}

mrand.Read(params.Payload)

msg, err := NewSentMessage(&params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}

e, err := msg.Wrap(&params)
if err != nil {
t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err)
}

f := Filter{KeySym: symKey, KeyAsym: asymKey}

decrypted := e.Open(&f)
if decrypted != nil {
t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed)
}
}
17 changes: 11 additions & 6 deletions whisper/whisperv6/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func NewFilters(w *Whisper) *Filters {
}

func (fs *Filters) Install(watcher *Filter) (string, error) {
if watcher.KeySym != nil && watcher.KeyAsym != nil {
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
}

if watcher.Messages == nil {
watcher.Messages = make(map[common.Hash]*ReceivedMessage)
}
Expand Down Expand Up @@ -175,6 +179,9 @@ func (f *Filter) Retrieve() (all []*ReceivedMessage) {
return all
}

// MatchMessage checks if the filter matches an already decrypted
// message (i.e. a Message that has already been handled by
// MatchEnvelope when checked by a previous filter)
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
if f.PoW > 0 && msg.PoW < f.PoW {
return false
Expand All @@ -188,17 +195,15 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
return false
}

// MatchEvelope checks if it's worth decrypting the message. If
// it returns `true`, client code is expected to attempt decrypting
// the message and subsequently call MatchMessage.
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
return f.MatchTopic(envelope.Topic)
}

func (f *Filter) MatchTopic(topic TopicType) bool {
Expand Down
36 changes: 30 additions & 6 deletions whisper/whisperv6/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,36 @@ func TestInstallIdenticalFilters(t *testing.T) {
}
}

func TestInstallFilterWithSymAndAsymKeys(t *testing.T) {
InitSingleTest()

w := New(&Config{})
filters := NewFilters(w)
filter1, _ := generateFilter(t, true)

asymKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("Unable to create asymetric keys: %v", err)
}

// Copy the first filter since some of its fields
// are randomly gnerated.
filter := &Filter{
KeySym: filter1.KeySym,
KeyAsym: asymKey,
Topics: filter1.Topics,
PoW: filter1.PoW,
AllowP2P: filter1.AllowP2P,
Messages: make(map[common.Hash]*ReceivedMessage),
}

_, err = filters.Install(filter)

if err == nil {
t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed)
}
}

func TestComparePubKey(t *testing.T) {
InitSingleTest()

Expand Down Expand Up @@ -312,12 +342,6 @@ func TestMatchEnvelope(t *testing.T) {
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)
Expand Down
46 changes: 27 additions & 19 deletions whisper/whisperv6/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ReceivedMessage struct {
Payload []byte
Padding []byte
Signature []byte
Salt []byte

PoW float64 // Proof of work as described in the Whisper spec
Sent uint32 // Time when the message was posted into the network
Expand Down Expand Up @@ -196,31 +197,31 @@ func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {

// 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) {
func (msg *sentMessage) encryptSymmetric(key []byte) (err error) {
if !validateSymmetricKey(key) {
return nil, errors.New("invalid key provided for symmetric encryption")
return errors.New("invalid key provided for symmetric encryption")
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
return err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
return err
}

// never use more than 2^32 random nonces with a given key
nonce = make([]byte, aesgcm.NonceSize())
_, err = crand.Read(nonce)
salt := make([]byte, aesgcm.NonceSize())
_, err = crand.Read(salt)
if err != nil {
return nil, err
} else if !validateSymmetricKey(nonce) {
return nil, errors.New("crypto/rand failed to generate nonce")
return err
} else if !validateSymmetricKey(salt) {
return errors.New("crypto/rand failed to generate salt")
}

msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil)
return nonce, nil
msg.Raw = append(aesgcm.Seal(nil, salt, msg.Raw, nil), salt...)
return nil
}

// Wrap bundles the message into an Envelope to transmit over the network.
Expand All @@ -233,19 +234,18 @@ func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
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)
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)
envelope = NewEnvelope(options.TTL, options.Topic, msg)
if err = envelope.Seal(options); err != nil {
return nil, err
}
Expand All @@ -254,7 +254,14 @@ func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er

// 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 {
func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
// In v6, symmetric messages are expected to contain the 12-byte
// "salt" at the end of the payload.
if len(msg.Raw) < AESNonceLength {
return errors.New("missing salt or invalid payload in symmetric message")
}
salt := msg.Raw[len(msg.Raw)-AESNonceLength:]

block, err := aes.NewCipher(key)
if err != nil {
return err
Expand All @@ -263,15 +270,16 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, nonce []byte) error {
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")
if len(salt) != aesgcm.NonceSize() {
log.Error("decrypting the message", "AES salt size", len(salt))
return errors.New("wrong AES salt size")
}
decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-AESNonceLength], nil)
if err != nil {
return err
}
msg.Raw = decrypted
msg.Salt = salt
return nil
}

Expand Down
Loading