whisper: polish the messages, fix some bugs, tests

Bugs fixed:
  - Use randomly generated flags as the spec required.
  - During envelope opening check the first bit only for signature.
This commit is contained in:
Péter Szilágyi 2015-04-10 15:53:21 +03:00
parent 7e54a9c07f
commit 7d8ce53eca
6 changed files with 213 additions and 92 deletions

View File

@ -40,7 +40,7 @@ func NewEnvelope(ttl time.Duration, topics [][]byte, data *Message) *Envelope {
Expiry: uint32(exp.Unix()), Expiry: uint32(exp.Unix()),
TTL: uint32(ttl.Seconds()), TTL: uint32(ttl.Seconds()),
Topics: topics, Topics: topics,
Data: data.Bytes(), Data: data.bytes(),
Nonce: 0, Nonce: 0,
} }
} }
@ -49,32 +49,32 @@ func (self *Envelope) Seal(pow time.Duration) {
self.proveWork(pow) self.proveWork(pow)
} }
func (self *Envelope) Open(prv *ecdsa.PrivateKey) (msg *Message, err error) { func (self *Envelope) Open(key *ecdsa.PrivateKey) (msg *Message, err error) {
data := self.Data data := self.Data
var message Message
dataStart := 1
if data[0] > 0 {
if len(data) < 66 {
return nil, fmt.Errorf("unable to open envelope. First bit set but len(data) < 66")
}
dataStart = 66
message.Flags = data[0]
message.Signature = data[1:66]
}
payload := data[dataStart:] message := Message{
if prv != nil { Flags: data[0],
message.Payload, err = crypto.Decrypt(prv, payload) }
data = data[1:]
if message.Flags&128 == 128 {
if len(data) < 65 {
return nil, fmt.Errorf("unable to open envelope. First bit set but len(data) < 65")
}
message.Signature, data = data[:65], data[65:]
}
message.Payload = data
if key != nil {
message.Payload, err = crypto.Decrypt(key, message.Payload)
switch err { switch err {
case nil: // OK case nil: // OK
case ecies.ErrInvalidPublicKey: // Payload isn't encrypted case ecies.ErrInvalidPublicKey: // Payload isn't encrypted
message.Payload = payload
return &message, err return &message, err
default: default:
return nil, fmt.Errorf("unable to open envelope. Decrypt failed: %v", err) return nil, fmt.Errorf("unable to open envelope. Decrypt failed: %v", err)
} }
} }
return &message, nil return &message, nil
} }

View File

@ -69,10 +69,10 @@ func selfSend(shh *whisper.Whisper, payload []byte) error {
}) })
// Wrap the payload and encrypt it // Wrap the payload and encrypt it
msg := whisper.NewMessage(payload) msg := whisper.NewMessage(payload)
envelope, err := msg.Seal(whisper.DefaultPow, whisper.Opts{ envelope, err := msg.Wrap(whisper.DefaultPow, whisper.Options{
Ttl: whisper.DefaultTtl,
From: id, From: id,
To: &id.PublicKey, To: &id.PublicKey,
TTL: whisper.DefaultTimeToLive,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to seal message: %v", err) return fmt.Errorf("failed to seal message: %v", err)

View File

@ -1,7 +1,11 @@
// Contains the Whisper protocol Message element. For formal details please see
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
package whisper package whisper
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"math/rand"
"time" "time"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -9,8 +13,11 @@ import (
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
) )
// Message represents an end-user data packet to trasmit through the Whisper
// protocol. These are wrapped into Envelopes that need not be understood by
// intermediate nodes, just forwarded.
type Message struct { type Message struct {
Flags byte Flags byte // First bit it signature presence, rest reserved and should be random
Signature []byte Signature []byte
Payload []byte Payload []byte
Sent int64 Sent int64
@ -18,71 +25,95 @@ type Message struct {
To *ecdsa.PublicKey To *ecdsa.PublicKey
} }
// Options specifies the exact way a message should be wrapped into an Envelope.
type Options struct {
From *ecdsa.PrivateKey
To *ecdsa.PublicKey
TTL time.Duration
Topics [][]byte
}
// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
func NewMessage(payload []byte) *Message { func NewMessage(payload []byte) *Message {
return &Message{Flags: 0, Payload: payload, Sent: time.Now().Unix()} // Construct an initial flag set: bit #1 = 0 (no signature), rest random
flags := byte(rand.Intn(128))
// Assemble and return the message
return &Message{
Flags: flags,
Payload: payload,
Sent: time.Now().Unix(),
}
} }
func (self *Message) hash() []byte { // Wrap bundles the message into an Envelope to transmit over the network.
return crypto.Sha3(append([]byte{self.Flags}, self.Payload...)) //
// Pov (Proof Of Work) controls how much time to spend on hashing the message,
// inherently controlling its priority through the network (smaller hash, bigger
// priority).
//
// The user can control the amount of identity, privacy and encryption through
// the options parameter as follows:
// - options.From == nil && options.To == nil: anonymous broadcast
// - 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 (self *Message) Wrap(pow time.Duration, options Options) (*Envelope, error) {
// Use the default TTL if non was specified
if options.TTL == 0 {
options.TTL = DefaultTimeToLive
}
// Sign and encrypt the message if requested
if options.From != nil {
if err := self.sign(options.From); err != nil {
return nil, err
}
}
if options.To != nil {
if err := self.encrypt(options.To); err != nil {
return nil, err
}
}
// Wrap the processed message, seal it and return
envelope := NewEnvelope(options.TTL, options.Topics, self)
envelope.Seal(pow)
return envelope, nil
} }
// Sign calculates and sets the cryptographic signature for the message , also
// setting the sign flag.
func (self *Message) sign(key *ecdsa.PrivateKey) (err error) { func (self *Message) sign(key *ecdsa.PrivateKey) (err error) {
self.Flags = 1 self.Flags |= 1 << 7
self.Signature, err = crypto.Sign(self.hash(), key) self.Signature, err = crypto.Sign(self.hash(), key)
return return
} }
// Recover retrieves the public key of the message signer.
func (self *Message) Recover() *ecdsa.PublicKey { func (self *Message) Recover() *ecdsa.PublicKey {
defer func() { recover() }() // in case of invalid sig defer func() { recover() }() // in case of invalid signature
pub, err := crypto.SigToPub(self.hash(), self.Signature) pub, err := crypto.SigToPub(self.hash(), self.Signature)
if err != nil { if err != nil {
glog.V(logger.Error).Infof("Could not get pubkey from signature: ", err) glog.V(logger.Error).Infof("Could not get public key from signature: %v", err)
return nil return nil
} }
return pub return pub
} }
func (self *Message) Encrypt(to *ecdsa.PublicKey) (err error) { // Encrypt encrypts a message payload with a public key.
func (self *Message) encrypt(to *ecdsa.PublicKey) (err error) {
self.Payload, err = crypto.Encrypt(to, self.Payload) self.Payload, err = crypto.Encrypt(to, self.Payload)
if err != nil { return
return err
} }
return nil // Hash calculates the SHA3 checksum of the message flags and payload.
func (self *Message) hash() []byte {
return crypto.Sha3(append([]byte{self.Flags}, self.Payload...))
} }
func (self *Message) Bytes() []byte { // Bytes flattens the message contents (flags, signature and payload) into a
// single binary blob.
func (self *Message) bytes() []byte {
return append([]byte{self.Flags}, append(self.Signature, self.Payload...)...) return append([]byte{self.Flags}, append(self.Signature, self.Payload...)...)
} }
type Opts struct {
From *ecdsa.PrivateKey
To *ecdsa.PublicKey
Ttl time.Duration
Topics [][]byte
}
func (self *Message) Seal(pow time.Duration, opts Opts) (*Envelope, error) {
if opts.From != nil {
err := self.sign(opts.From)
if err != nil {
return nil, err
}
}
if opts.To != nil {
err := self.Encrypt(opts.To)
if err != nil {
return nil, err
}
}
if opts.Ttl == 0 {
opts.Ttl = DefaultTtl
}
envelope := NewEnvelope(opts.Ttl, opts.Topics, self)
envelope.Seal(pow)
return envelope, nil
}

View File

@ -3,48 +3,136 @@ package whisper
import ( import (
"bytes" "bytes"
"crypto/elliptic" "crypto/elliptic"
"fmt"
"testing" "testing"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
func TestSign(t *testing.T) { // Tests whether a message can be wrapped without any identity or encryption.
prv, _ := crypto.GenerateKey() func TestMessageSimpleWrap(t *testing.T) {
msg := NewMessage([]byte("hello world")) payload := []byte("hello world")
msg.sign(prv)
msg := NewMessage(payload)
if _, err := msg.Wrap(DefaultPow, Options{}); err != nil {
t.Fatalf("failed to wrap message: %v", err)
}
if msg.Flags&128 != 0 {
t.Fatalf("signature flag mismatch: have %d, want %d", (msg.Flags&128)>>7, 0)
}
if len(msg.Signature) != 0 {
t.Fatalf("signature found for simple wrapping: 0x%x", msg.Signature)
}
if bytes.Compare(msg.Payload, payload) != 0 {
t.Fatalf("payload mismatch after wrapping: have 0x%x, want 0x%x", msg.Payload, payload)
}
}
// Tests whether a message can be signed, and wrapped in plain-text.
func TestMessageCleartextSignRecover(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to create crypto key: %v", err)
}
payload := []byte("hello world")
msg := NewMessage(payload)
if _, err := msg.Wrap(DefaultPow, Options{
From: key,
}); err != nil {
t.Fatalf("failed to sign message: %v", err)
}
if msg.Flags&128 != 128 {
t.Fatalf("signature flag mismatch: have %d, want %d", (msg.Flags&128)>>7, 1)
}
if bytes.Compare(msg.Payload, payload) != 0 {
t.Fatalf("payload mismatch after signing: have 0x%x, want 0x%x", msg.Payload, payload)
}
pubKey := msg.Recover() pubKey := msg.Recover()
p1 := elliptic.Marshal(crypto.S256(), prv.PublicKey.X, prv.PublicKey.Y) if pubKey == nil {
t.Fatalf("failed to recover public key")
}
p1 := elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y)
p2 := elliptic.Marshal(crypto.S256(), pubKey.X, pubKey.Y) p2 := elliptic.Marshal(crypto.S256(), pubKey.X, pubKey.Y)
if !bytes.Equal(p1, p2) { if !bytes.Equal(p1, p2) {
t.Error("recovered pub key did not match") t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
} }
} }
func TestMessageEncryptDecrypt(t *testing.T) { // Tests whether a message can be encrypted and decrypted using an anonymous
prv1, _ := crypto.GenerateKey() // sender (i.e. no signature).
prv2, _ := crypto.GenerateKey() func TestMessageAnonymousEncryptDecrypt(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to create recipient crypto key: %v", err)
}
payload := []byte("hello world")
data := []byte("hello world") msg := NewMessage(payload)
msg := NewMessage(data) envelope, err := msg.Wrap(DefaultPow, Options{
envelope, err := msg.Seal(DefaultPow, Opts{ To: &key.PublicKey,
From: prv1,
To: &prv2.PublicKey,
}) })
if err != nil { if err != nil {
fmt.Println(err) t.Fatalf("failed to encrypt message: %v", err)
t.FailNow() }
if msg.Flags&128 != 0 {
t.Fatalf("signature flag mismatch: have %d, want %d", (msg.Flags&128)>>7, 0)
}
if len(msg.Signature) != 0 {
t.Fatalf("signature found for anonymous message: 0x%x", msg.Signature)
} }
msg1, err := envelope.Open(prv2) out, err := envelope.Open(key)
if err != nil { if err != nil {
t.Error(err) t.Fatalf("failed to open encrypted message: %v", err)
t.FailNow() }
if !bytes.Equal(out.Payload, payload) {
t.Error("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
}
} }
if !bytes.Equal(msg1.Payload, data) { // Tests whether a message can be properly signed and encrypted.
t.Error("encryption error. data did not match") func TestMessageFullCrypto(t *testing.T) {
fromKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to create sender crypto key: %v", err)
}
toKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to create recipient crypto key: %v", err)
}
payload := []byte("hello world")
msg := NewMessage(payload)
envelope, err := msg.Wrap(DefaultPow, Options{
From: fromKey,
To: &toKey.PublicKey,
})
if err != nil {
t.Fatalf("failed to encrypt message: %v", err)
}
if msg.Flags&128 != 128 {
t.Fatalf("signature flag mismatch: have %d, want %d", (msg.Flags&128)>>7, 1)
}
if len(msg.Signature) == 0 {
t.Fatalf("no signature found for signed message")
}
out, err := envelope.Open(toKey)
if err != nil {
t.Fatalf("failed to open encrypted message: %v", err)
}
if !bytes.Equal(out.Payload, payload) {
t.Error("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
}
pubKey := out.Recover()
if pubKey == nil {
t.Fatalf("failed to recover public key")
}
p1 := elliptic.Marshal(crypto.S256(), fromKey.PublicKey.X, fromKey.PublicKey.Y)
p2 := elliptic.Marshal(crypto.S256(), pubKey.X, pubKey.Y)
if !bytes.Equal(p1, p2) {
t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
} }
} }

View File

@ -28,7 +28,9 @@ type MessageEvent struct {
Message *Message Message *Message
} }
const DefaultTtl = 50 * time.Second const (
DefaultTimeToLive = 50 * time.Second
)
type Whisper struct { type Whisper struct {
protocol p2p.Protocol protocol p2p.Protocol

View File

@ -18,8 +18,8 @@ func TestEvent(t *testing.T) {
}) })
msg := NewMessage([]byte(fmt.Sprintf("Hello world. This is whisper-go. Incase you're wondering; the time is %v", time.Now()))) msg := NewMessage([]byte(fmt.Sprintf("Hello world. This is whisper-go. Incase you're wondering; the time is %v", time.Now())))
envelope, err := msg.Seal(DefaultPow, Opts{ envelope, err := msg.Wrap(DefaultPow, Options{
Ttl: DefaultTtl, TTL: DefaultTimeToLive,
From: id, From: id,
To: &id.PublicKey, To: &id.PublicKey,
}) })