diff --git a/whisper/shhapi/api.go b/whisper/shhapi/api.go index 01be0e9a9b..f33ede14e3 100644 --- a/whisper/shhapi/api.go +++ b/whisper/shhapi/api.go @@ -346,7 +346,7 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { // encrypt and send message := whisperv5.NewSentMessage(¶ms) - envelope, err := message.Wrap(params) + envelope, err := message.Wrap(¶ms) if err != nil { glog.V(logger.Error).Infof(err.Error()) return err @@ -497,7 +497,7 @@ func NewWhisperMessage(message *whisperv5.ReceivedMessage) WhisperMessage { return WhisperMessage{ Payload: common.ToHex(message.Payload), Padding: common.ToHex(message.Padding), - From: common.ToHex(crypto.FromECDSAPub(message.Recover())), + From: common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())), To: common.ToHex(crypto.FromECDSAPub(message.Dst)), Sent: message.Sent, TTL: message.TTL, diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go index f3b5458d72..5c6b882c68 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -85,7 +85,7 @@ func (e *Envelope) Ver() uint64 { // 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) { +func (e *Envelope) Seal(options *MessageParams) { var target int if options.PoW == 0 { // adjust for the duration of Seal() execution only if execution time is predefined unconditionally diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index b86110849e..057e5741a0 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -136,13 +136,13 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { return false } - if f.Src != nil && !isEqual(msg.Src, f.Src) { + if f.Src != nil && !isPubKeyEqual(msg.Src, f.Src) { return false } if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() { // if Dst match, ignore the topic - return isEqual(f.Dst, msg.Dst) + return isPubKeyEqual(f.Dst, msg.Dst) } else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() { // check if that both the key and the topic match if f.SymKeyHash == msg.SymKeyHash { @@ -183,7 +183,7 @@ func (f *Filter) MatchEnvelope(envelope *Envelope) bool { return false } -func isEqual(a, b *ecdsa.PublicKey) bool { +func isPubKeyEqual(a, b *ecdsa.PublicKey) bool { if !ValidatePublicKey(a) { return false } else if !ValidatePublicKey(b) { diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go new file mode 100644 index 0000000000..e92b528d3e --- /dev/null +++ b/whisper/whisperv5/filter_test.go @@ -0,0 +1,40 @@ +// 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 . + +package whisperv5 + +/* +import "testing" + +var topicXxxxxxxxxxxTests = []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 TestXxxxxxxxxxxx(x *testing.T) { + for i, tst := range topicStringTests { + s := tst.topic.String() + if s != tst.str { + x.Errorf("failed test %d: have %s, want %s.", i, s, tst.str) + } + } +} +*/ diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index f192131484..176676caca 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -148,13 +148,15 @@ func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error { glog.V(logger.Error).Infof("Trying to sign a message which was already signed") return nil } + + msg.Raw[0] |= signatureFlag hash := crypto.Keccak256(msg.Raw) signature, err := crypto.Sign(hash, key) if err != nil { - msg.Raw = append(msg.Raw, signature...) - msg.Raw[0] |= signatureFlag + msg.Raw[0] &= ^signatureFlag // clear the flag return err } + msg.Raw = append(msg.Raw, signature...) return nil } @@ -223,12 +225,13 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte, // - options.From != nil && options.To == nil: signed broadcast (known sender) // - options.From == nil && options.To != nil: encrypted anonymous message // - options.From != nil && options.To != nil: encrypted signed message -func (msg *SentMessage) Wrap(options MessageParams) (envelope *Envelope, err error) { +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 { + err = msg.sign(options.Src) + if err != nil { return nil, err } } @@ -305,7 +308,7 @@ func (msg *ReceivedMessage) Validate() bool { return false } msg.Signature = msg.Raw[end:] - msg.Src = msg.Recover() + msg.Src = msg.SigToPubKey() if msg.Src == nil { return false } @@ -317,7 +320,7 @@ func (msg *ReceivedMessage) Validate() bool { } msg.Payload = msg.Raw[1+padSize : end] - return msg.isSymmetricEncryption() != msg.isAsymmetricEncryption() + return true } // extractPadding extracts the padding from raw message. @@ -338,7 +341,7 @@ func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { } // Recover retrieves the public key of the message signer. -func (msg *ReceivedMessage) Recover() *ecdsa.PublicKey { +func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { defer func() { recover() }() // in case of invalid signature pub, err := crypto.SigToPub(msg.hash(), msg.Signature) diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go new file mode 100644 index 0000000000..2d4e8e2b13 --- /dev/null +++ b/whisper/whisperv5/message_test.go @@ -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 . + +package whisperv5 + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func copyFromBuf(dst []byte, src []byte, beg int) int { + copy(dst, src[beg:]) + return beg + len(dst) +} + +func generateMessageParams(seed int64) (*MessageParams, error) { + var err error + var p MessageParams + + buf := make([]byte, 1024) + _, err = rand.Read(buf) + if err != nil { + return nil, err + } + + p.TTL = uint32(rand.Intn(1024)) + sz := rand.Intn(400) + p.Payload = make([]byte, sz) + p.Padding = make([]byte, padSizeLimitUpper) + p.KeySym = make([]byte, aesKeyLength) + + var b int + b = copyFromBuf(p.Payload, buf, b) + b = copyFromBuf(p.Padding, buf, b) + b = copyFromBuf(p.KeySym, buf, b) + p.Topic = BytesToTopic(buf[b:]) + + p.Src, err = crypto.GenerateKey() + if err != nil { + return nil, err + } + + // p.Dst, p.PoW, p.WorkTime are not set + return &p, nil +} + +func singleMessageTest(x *testing.T, seed int64, symmetric bool) { + params, err := generateMessageParams(seed) + if err != nil { + x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err) + return + } + + key, err := crypto.GenerateKey() + if err != nil { + x.Errorf("failed GenerateKey with seed %d: %s.", seed, err) + return + } + + if !symmetric { + params.KeySym = nil + params.Dst = &key.PublicKey + } + + text := make([]byte, 0, 512) + steg := make([]byte, 0, 512) + raw := make([]byte, 0, 1024) + text = append(text, params.Payload...) + steg = append(steg, params.Padding...) + raw = append(raw, params.Padding...) + + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + x.Errorf("failed Wrap with seed %d: %s.", seed, err) + return + } + + var decrypted *ReceivedMessage + if symmetric { + decrypted, err = env.OpenSymmetric(params.KeySym) + } else { + decrypted, err = env.OpenAsymmetric(key) + } + + if err != nil { + x.Errorf("failed to encrypt with seed %d: %s.", seed, err) + return + } + + if !decrypted.Validate() { + x.Errorf("failed to validate with seed %d.", seed) + return + } + + padsz := len(decrypted.Padding) + if bytes.Compare(steg[:padsz], decrypted.Padding) != 0 { + x.Errorf("failed with seed %d: compare padding.", seed) + return + } + if bytes.Compare(text, decrypted.Payload) != 0 { + x.Errorf("failed with seed %d: compare payload.", seed) + return + } + if len(decrypted.Signature) != signatureLength { + x.Errorf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) + return + } + if !isPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { + x.Errorf("failed with seed %d: signature mismatch.", seed) + return + } +} + +func TestMessageEncryption(x *testing.T) { + seed := time.Now().Unix() + rand.Seed(seed) + var symmetric bool + + for i := 0; i < 256; i++ { + singleMessageTest(x, seed, symmetric) + symmetric = !symmetric + } +} + +func DebugSingleMessageTest(x *testing.T) { + seed := int64(1476726903) + symmetric := seed%2 != 0 + + key, err := crypto.GenerateKey() + if err != nil { + x.Errorf("failed GenerateKey with seed %d: %s.", seed, err) + return + } + + params, err := generateMessageParams(seed) + if err != nil { + x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err) + return + } + msg := NewSentMessage(params) + + text := make([]byte, 0, 512) + steg := make([]byte, 0, 512) + raw := make([]byte, 0, 1024) + text = append(text, params.Payload...) + steg = append(steg, params.Padding...) + raw = append(raw, params.Padding...) + + if !symmetric { + params.KeySym = nil + params.Dst = &key.PublicKey + } + + if len(params.KeySym) == 0 { + x.Errorf("failed key 12 with seed %d.", seed) + return + } + + env, err := msg.Wrap(params) + if err != nil { + x.Errorf("failed Wrap with seed %d: %s.", seed, err) + return + } + + var decrypted *ReceivedMessage + if symmetric { + decrypted, err = env.OpenSymmetric(params.KeySym) + } else { + decrypted, err = env.OpenAsymmetric(key) + } + + if err != nil { + x.Errorf("failed to encrypt with seed %d: %s.", seed, err) + return + } + + ok := decrypted.Validate() + if !ok { + x.Errorf("failed to validate with seed %d.", seed) + return + } + + sz := len(decrypted.Padding) + if bytes.Compare(steg[:sz], decrypted.Padding) != 0 { + x.Errorf("failed with seed %d: compare padding.", seed) + return + } + if bytes.Compare(text, decrypted.Payload) != 0 { + x.Errorf("failed with seed %d: compare payload.", seed) + return + } + if len(decrypted.Signature) != signatureLength { + x.Errorf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) + return + } + if !isPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { + x.Errorf("failed with seed %d: signature mismatch:\n%v and \n%v.", seed, *decrypted.Src, params.Src.PublicKey) + return + } +} + +func TestMessageSeal(x *testing.T) { + seed := int64(1476726903) + rand.Seed(seed) + + params, err := generateMessageParams(seed) + if err != nil { + x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err) + return + } + + msg := NewSentMessage(params) + params.TTL = 1 + params.WorkTime = 3 + env, err := msg.Wrap(params) + if err != nil { + x.Errorf("failed Wrap with seed %d: %s.", seed, err) + return + } + + pow := env.PoW() + h := env.Hash() + bits := common.FirstBitSet(common.BigD(h.Bytes())) + fmt.Printf("pow = %f, bits = %d, datalen = %d, hash = %x \n", pow, bits, len(env.Data), h) + +} diff --git a/whisper/whisperv5/topic_test.go b/whisper/whisperv5/topic_test.go new file mode 100644 index 0000000000..c2a940b79a --- /dev/null +++ b/whisper/whisperv5/topic_test.go @@ -0,0 +1,136 @@ +// 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 . + +package whisperv5 + +import "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(x *testing.T) { + for i, tst := range topicStringTests { + s := tst.topic.String() + if s != tst.str { + x.Errorf("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}, +} + +func TestBytesToTopic(x *testing.T) { + for i, tst := range bytesToTopicTests { + t := BytesToTopic(tst.data) + if t != tst.topic { + x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic) + } + } +} + +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")}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("00000000")}, + {topic: TopicType{0x00, 0x80, 0x01, 0x00}, data: []byte("00800100")}, + {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte("007f80ff")}, + {topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte("ff807f00")}, + {topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte("f26e7779")}, +} + +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("00000001")}, +} + +func TestUnmarshalTestsGood(x *testing.T) { + for i, tst := range unmarshalTestsGood { + var t TopicType + err := t.UnmarshalJSON(tst.data) + if err != nil { + x.Errorf("failed test %d. input: %v.", i, tst.data) + } else if t != tst.topic { + x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic) + } + } +} + +func TestUnmarshalTestsBad(x *testing.T) { + // in this test UnmarshalJSON() is supposed to fail + for i, tst := range unmarshalTestsBad { + var t TopicType + err := t.UnmarshalJSON(tst.data) + if err == nil { + x.Errorf("failed test %d. input: %v.", i, tst.data) + } + } +} + +func TestUnmarshalTestsUgly(x *testing.T) { + // in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong + for i, tst := range unmarshalTestsUgly { + var t TopicType + err := t.UnmarshalJSON(tst.data) + if err != nil { + x.Errorf("failed test %d. input: %v.", i, tst.data) + } else if t == tst.topic { + x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic) + } + } +} diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 1183db78e6..b8eac6684c 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -533,10 +533,10 @@ func validateSymmetricKey(k []byte) bool { func containsOnlyZeros(data []byte) bool { for _, b := range data { if b != 0 { - return true + return false } } - return false + return true } func bytesToIntLittleEndian(b []byte) (res uint64) {