aboutsummaryrefslogtreecommitdiffstats
path: root/whisper
diff options
context:
space:
mode:
Diffstat (limited to 'whisper')
-rw-r--r--whisper/doc.go16
-rw-r--r--whisper/envelope.go55
-rw-r--r--whisper/filter.go2
-rw-r--r--whisper/main.go16
-rw-r--r--whisper/message.go11
-rw-r--r--whisper/messages_test.go5
-rw-r--r--whisper/peer.go4
-rw-r--r--whisper/util.go11
-rw-r--r--whisper/whisper.go103
-rw-r--r--whisper/whisper_test.go11
10 files changed, 145 insertions, 89 deletions
diff --git a/whisper/doc.go b/whisper/doc.go
new file mode 100644
index 000000000..986df8fb9
--- /dev/null
+++ b/whisper/doc.go
@@ -0,0 +1,16 @@
+/*
+Package whisper implements the Whisper PoC-1.
+
+(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
+
+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 whisper
diff --git a/whisper/envelope.go b/whisper/envelope.go
index 683e88128..d30397c98 100644
--- a/whisper/envelope.go
+++ b/whisper/envelope.go
@@ -1,14 +1,13 @@
package whisper
import (
- "bytes"
"crypto/ecdsa"
"encoding/binary"
"fmt"
- "io"
"time"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -27,22 +26,6 @@ type Envelope struct {
hash Hash
}
-func NewEnvelopeFromReader(reader io.Reader) (*Envelope, error) {
- var envelope Envelope
-
- buf := new(bytes.Buffer)
- buf.ReadFrom(reader)
-
- h := H(crypto.Sha3(buf.Bytes()))
- if err := rlp.Decode(buf, &envelope); err != nil {
- return nil, err
- }
-
- envelope.hash = h
-
- return &envelope, nil
-}
-
func (self *Envelope) Hash() Hash {
if self.hash == EmptyHash {
self.hash = H(crypto.Sha3(ethutil.Encode(self)))
@@ -73,10 +56,16 @@ func (self *Envelope) Open(prv *ecdsa.PrivateKey) (msg *Message, err error) {
message.Flags = data[0]
message.Signature = data[1:66]
}
- message.Payload = data[dataStart:]
+
+ payload := data[dataStart:]
if prv != nil {
- message.Payload, err = crypto.Decrypt(prv, message.Payload)
- if err != nil {
+ message.Payload, err = crypto.Decrypt(prv, payload)
+ switch err {
+ case nil: // OK
+ case ecies.ErrInvalidPublicKey: // Payload isn't encrypted
+ message.Payload = payload
+ return &message, err
+ default:
return nil, fmt.Errorf("unable to open envelope. Decrypt failed: %v", err)
}
}
@@ -119,3 +108,27 @@ func (self *Envelope) withoutNonce() interface{} {
func (self *Envelope) RlpData() interface{} {
return []interface{}{self.Expiry, self.Ttl, ethutil.ByteSliceToInterface(self.Topics), self.Data, self.Nonce}
}
+
+func (self *Envelope) DecodeRLP(s *rlp.Stream) error {
+ var extenv struct {
+ Expiry uint32
+ Ttl uint32
+ Topics [][]byte
+ Data []byte
+ Nonce uint32
+ }
+ if err := s.Decode(&extenv); err != nil {
+ return err
+ }
+
+ self.Expiry = extenv.Expiry
+ self.Ttl = extenv.Ttl
+ self.Topics = extenv.Topics
+ self.Data = extenv.Data
+ self.Nonce = extenv.Nonce
+
+ // TODO We should use the stream directly here.
+ self.hash = H(crypto.Sha3(ethutil.Encode(self)))
+
+ return nil
+}
diff --git a/whisper/filter.go b/whisper/filter.go
index 4315aa556..b33f2c1a2 100644
--- a/whisper/filter.go
+++ b/whisper/filter.go
@@ -3,7 +3,7 @@ package whisper
import "crypto/ecdsa"
type Filter struct {
- To *ecdsa.PrivateKey
+ To *ecdsa.PublicKey
From *ecdsa.PublicKey
Topics [][]byte
Fn func(*Message)
diff --git a/whisper/main.go b/whisper/main.go
index 2ee2f3ff1..9f35dbb8d 100644
--- a/whisper/main.go
+++ b/whisper/main.go
@@ -5,14 +5,12 @@ package main
import (
"fmt"
"log"
- "net"
"os"
- "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/whisper"
- "github.com/obscuren/secp256k1-go"
)
func main() {
@@ -20,12 +18,12 @@ func main() {
pub, _ := secp256k1.GenerateKeyPair()
- whisper := whisper.New(&event.TypeMux{})
+ whisper := whisper.New()
srv := p2p.Server{
MaxPeers: 10,
Identity: p2p.NewSimpleClientIdentity("whisper-go", "1.0", "", string(pub)),
- ListenAddr: ":30303",
+ ListenAddr: ":30300",
NAT: p2p.UPNP(),
Protocols: []p2p.Protocol{whisper.Protocol()},
@@ -35,13 +33,5 @@ func main() {
os.Exit(1)
}
- // add seed peers
- seed, err := net.ResolveTCPAddr("tcp", "poc-7.ethdev.com:30300")
- if err != nil {
- fmt.Println("couldn't resolve:", err)
- os.Exit(1)
- }
- srv.SuggestPeer(seed.IP, seed.Port, nil)
-
select {}
}
diff --git a/whisper/message.go b/whisper/message.go
index db0110b4a..5d9e5b5c1 100644
--- a/whisper/message.go
+++ b/whisper/message.go
@@ -11,10 +11,13 @@ type Message struct {
Flags byte
Signature []byte
Payload []byte
+ Sent int64
+
+ To *ecdsa.PublicKey
}
func NewMessage(payload []byte) *Message {
- return &Message{Flags: 0, Payload: payload}
+ return &Message{Flags: 0, Payload: payload, Sent: time.Now().Unix()}
}
func (self *Message) hash() []byte {
@@ -67,7 +70,11 @@ func (self *Message) Seal(pow time.Duration, opts Opts) (*Envelope, error) {
}
}
- envelope := NewEnvelope(DefaultTtl, opts.Topics, self)
+ if opts.Ttl == 0 {
+ opts.Ttl = DefaultTtl
+ }
+
+ envelope := NewEnvelope(opts.Ttl, opts.Topics, self)
envelope.Seal(pow)
return envelope, nil
diff --git a/whisper/messages_test.go b/whisper/messages_test.go
index cba103011..93caa31b3 100644
--- a/whisper/messages_test.go
+++ b/whisper/messages_test.go
@@ -40,12 +40,11 @@ func TestMessageEncryptDecrypt(t *testing.T) {
msg1, err := envelope.Open(prv2)
if err != nil {
- fmt.Println(err)
+ t.Error(err)
t.FailNow()
}
if !bytes.Equal(msg1.Payload, data) {
- fmt.Println("encryption error. data did not match")
- t.FailNow()
+ t.Error("encryption error. data did not match")
}
}
diff --git a/whisper/peer.go b/whisper/peer.go
index d42b374b5..332ddd22a 100644
--- a/whisper/peer.go
+++ b/whisper/peer.go
@@ -55,7 +55,7 @@ out:
case <-relay.C:
err := self.broadcast(self.host.envelopes())
if err != nil {
- self.peer.Infoln(err)
+ self.peer.Infoln("broadcast err:", err)
break out
}
@@ -81,7 +81,7 @@ func (self *peer) broadcast(envelopes []*Envelope) error {
if err := self.ws.WriteMsg(msg); err != nil {
return err
}
- self.peer.Infoln("broadcasted", i, "message(s)")
+ self.peer.DebugDetailln("broadcasted", i, "message(s)")
}
return nil
diff --git a/whisper/util.go b/whisper/util.go
index abef1d667..7a222395f 100644
--- a/whisper/util.go
+++ b/whisper/util.go
@@ -18,10 +18,19 @@ func Topics(data [][]byte) [][]byte {
return d
}
-func TopicsFromString(data []string) [][]byte {
+func TopicsFromString(data ...string) [][]byte {
d := make([][]byte, len(data))
for i, str := range data {
d[i] = hashTopic([]byte(str))
}
return d
}
+
+func bytesToMap(s [][]byte) map[string]struct{} {
+ m := make(map[string]struct{})
+ for _, topic := range s {
+ m[string(topic)] = struct{}{}
+ }
+
+ return m
+}
diff --git a/whisper/whisper.go b/whisper/whisper.go
index 356debd1c..50c2f98fd 100644
--- a/whisper/whisper.go
+++ b/whisper/whisper.go
@@ -4,12 +4,13 @@ import (
"bytes"
"crypto/ecdsa"
"errors"
- "fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/event/filter"
+ "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"gopkg.in/fatih/set.v0"
)
@@ -47,6 +48,8 @@ type MessageEvent struct {
const DefaultTtl = 50 * time.Second
+var wlogger = logger.NewLogger("SHH")
+
type Whisper struct {
protocol p2p.Protocol
filters *filter.Filters
@@ -57,7 +60,7 @@ type Whisper struct {
quit chan struct{}
- keys []*ecdsa.PrivateKey
+ keys map[string]*ecdsa.PrivateKey
}
func New() *Whisper {
@@ -66,19 +69,9 @@ func New() *Whisper {
filters: filter.New(),
expiry: make(map[uint32]*set.SetNonTS),
quit: make(chan struct{}),
+ keys: make(map[string]*ecdsa.PrivateKey),
}
whisper.filters.Start()
- go whisper.update()
-
- // XXX TODO REMOVE TESTING CODE
- msg := NewMessage([]byte(fmt.Sprintf("Hello world. This is whisper-go. Incase you're wondering; the time is %v", time.Now())))
- envelope, _ := msg.Seal(DefaultPow, Opts{
- Ttl: DefaultTtl,
- })
- if err := whisper.Send(envelope); err != nil {
- fmt.Println(err)
- }
- // XXX TODO REMOVE TESTING CODE
// p2p whisper sub protocol handler
whisper.protocol = p2p.Protocol{
@@ -91,6 +84,11 @@ func New() *Whisper {
return whisper
}
+func (self *Whisper) Start() {
+ wlogger.Infoln("Whisper started")
+ go self.update()
+}
+
func (self *Whisper) Stop() {
close(self.quit)
}
@@ -104,30 +102,47 @@ func (self *Whisper) NewIdentity() *ecdsa.PrivateKey {
if err != nil {
panic(err)
}
- self.keys = append(self.keys, key)
+
+ self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
return key
}
-func (self *Whisper) HasIdentity(key *ecdsa.PrivateKey) bool {
- for _, key := range self.keys {
- if key.D.Cmp(key.D) == 0 {
- return true
- }
- }
- return false
+func (self *Whisper) HasIdentity(key *ecdsa.PublicKey) bool {
+ return self.keys[string(crypto.FromECDSAPub(key))] != nil
+}
+
+func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
+ return self.keys[string(crypto.FromECDSAPub(key))]
}
func (self *Whisper) Watch(opts Filter) int {
return self.filters.Install(filter.Generic{
- Str1: string(crypto.FromECDSA(opts.To)),
+ Str1: string(crypto.FromECDSAPub(opts.To)),
Str2: string(crypto.FromECDSAPub(opts.From)),
+ Data: bytesToMap(opts.Topics),
Fn: func(data interface{}) {
opts.Fn(data.(*Message))
},
})
}
+func (self *Whisper) Messages(id int) (messages []*Message) {
+ filter := self.filters.Get(id)
+ if filter != nil {
+ for _, e := range self.messages {
+ if msg, key := self.open(e); msg != nil {
+ f := createFilter(msg, e.Topics, key)
+ if self.filters.Match(filter, f) {
+ messages = append(messages, msg)
+ }
+ }
+ }
+ }
+
+ return
+}
+
// Main handler for passing whisper messages to whisper peer objects
func (self *Whisper) msgHandler(peer *p2p.Peer, ws p2p.MsgReadWriter) error {
wpeer := NewPeer(self, peer, ws)
@@ -146,17 +161,19 @@ func (self *Whisper) msgHandler(peer *p2p.Peer, ws p2p.MsgReadWriter) error {
return err
}
- envelope, err := NewEnvelopeFromReader(msg.Payload)
- if err != nil {
+ var envelopes []*Envelope
+ if err := msg.Decode(&envelopes); err != nil {
peer.Infoln(err)
continue
}
- if err := self.add(envelope); err != nil {
- // TODO Punish peer here. Invalid envelope.
- peer.Infoln(err)
+ for _, envelope := range envelopes {
+ if err := self.add(envelope); err != nil {
+ // TODO Punish peer here. Invalid envelope.
+ peer.Infoln(err)
+ }
+ wpeer.addKnown(envelope)
}
- wpeer.addKnown(envelope)
}
}
@@ -177,9 +194,11 @@ func (self *Whisper) add(envelope *Envelope) error {
if !self.expiry[envelope.Expiry].Has(hash) {
self.expiry[envelope.Expiry].Add(hash)
- self.postEvent(envelope)
+ go self.postEvent(envelope)
}
+ wlogger.DebugDetailf("added whisper envelope %x\n", envelope)
+
return nil
}
@@ -229,18 +248,30 @@ func (self *Whisper) envelopes() (envelopes []*Envelope) {
}
func (self *Whisper) postEvent(envelope *Envelope) {
+ if message, key := self.open(envelope); message != nil {
+ self.filters.Notify(createFilter(message, envelope.Topics, key), message)
+ }
+}
+
+func (self *Whisper) open(envelope *Envelope) (*Message, *ecdsa.PrivateKey) {
for _, key := range self.keys {
- if message, err := envelope.Open(key); err == nil {
- // Create a custom filter?
- self.filters.Notify(filter.Generic{
- Str1: string(crypto.FromECDSA(key)), Str2: string(crypto.FromECDSAPub(message.Recover())),
- }, message)
- } else {
- fmt.Println(err)
+ if message, err := envelope.Open(key); err == nil || (err != nil && err == ecies.ErrInvalidPublicKey) {
+ message.To = &key.PublicKey
+
+ return message, key
}
}
+
+ return nil, nil
}
func (self *Whisper) Protocol() p2p.Protocol {
return self.protocol
}
+
+func createFilter(message *Message, topics [][]byte, key *ecdsa.PrivateKey) filter.Filter {
+ return filter.Generic{
+ Str1: string(crypto.FromECDSAPub(&key.PublicKey)), Str2: string(crypto.FromECDSAPub(message.Recover())),
+ Data: bytesToMap(topics),
+ }
+}
diff --git a/whisper/whisper_test.go b/whisper/whisper_test.go
index 107cb8c97..3e3945a0a 100644
--- a/whisper/whisper_test.go
+++ b/whisper/whisper_test.go
@@ -6,21 +6,12 @@ import (
"time"
)
-func TestKeyManagement(t *testing.T) {
- whisper := New()
-
- key := whisper.NewIdentity()
- if !whisper.HasIdentity(key) {
- t.Error("expected whisper to have identify")
- }
-}
-
func TestEvent(t *testing.T) {
res := make(chan *Message, 1)
whisper := New()
id := whisper.NewIdentity()
whisper.Watch(Filter{
- To: id,
+ To: &id.PublicKey,
Fn: func(msg *Message) {
res <- msg
},