diff --git a/waku/v2/protocol/relay/validators.go b/waku/v2/protocol/relay/validators.go index ed145fdf..6e99fd36 100644 --- a/waku/v2/protocol/relay/validators.go +++ b/waku/v2/protocol/relay/validators.go @@ -4,7 +4,9 @@ import ( "context" "crypto/ecdsa" "crypto/elliptic" + "encoding/binary" "encoding/hex" + "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" @@ -13,19 +15,46 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/waku-org/go-waku/waku/v2/hash" "github.com/waku-org/go-waku/waku/v2/protocol/pb" + "github.com/waku-org/go-waku/waku/v2/timesource" "go.uber.org/zap" proto "google.golang.org/protobuf/proto" ) // Application level message hash func MsgHash(pubSubTopic string, msg *pb.WakuMessage) []byte { - // TODO: Other fields? - return hash.SHA256([]byte(pubSubTopic), msg.Payload, []byte(msg.ContentTopic)) + timestampBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(timestampBytes, uint64(msg.Timestamp)) + + var ephemeralByte byte + if msg.Ephemeral { + ephemeralByte = 1 + } + + return hash.SHA256( + []byte(pubSubTopic), + msg.Payload, + []byte(msg.ContentTopic), + timestampBytes, + []byte{ephemeralByte}, + ) +} + +const MessageWindowDuration = time.Minute * 5 + +func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool { + if msg.Timestamp == 0 { + return false + } + + now := t.Now() + msgTime := time.Unix(0, msg.Timestamp) + + return now.Sub(msgTime).Abs() <= MessageWindowDuration } type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool -func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn { +func validatorFnBuilder(t timesource.Timesource, topic string, publicKey *ecdsa.PublicKey) validatorFn { pubkBytes := crypto.FromECDSAPub(publicKey) return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { msg := new(pb.WakuMessage) @@ -34,6 +63,10 @@ func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn { return false } + if !withinTimeWindow(t, msg) { + return false + } + msgHash := MsgHash(topic, msg) signature := msg.Meta @@ -43,7 +76,7 @@ func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn { func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.PublicKey) error { w.log.Info("adding validator to signed topic", zap.String("topic", topic), zap.String("publicKey", hex.EncodeToString(elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)))) - err := w.pubsub.RegisterTopicValidator(topic, validatorFnBuilder(topic, publicKey)) + err := w.pubsub.RegisterTopicValidator(topic, validatorFnBuilder(w.timesource, topic, publicKey)) if err != nil { return err } diff --git a/waku/v2/protocol/relay/validators_test.go b/waku/v2/protocol/relay/validators_test.go index 8f2bff5b..6377d6b7 100644 --- a/waku/v2/protocol/relay/validators_test.go +++ b/waku/v2/protocol/relay/validators_test.go @@ -5,17 +5,38 @@ import ( "context" "encoding/hex" "testing" + "time" "github.com/ethereum/go-ethereum/crypto" pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/stretchr/testify/require" "github.com/waku-org/go-waku/waku/v2/protocol/pb" - "github.com/waku-org/go-waku/waku/v2/timesource" - "github.com/waku-org/go-waku/waku/v2/utils" "google.golang.org/protobuf/proto" ) +type FakeTimesource struct { + now time.Time +} + +func NewFakeTimesource(now time.Time) FakeTimesource { + return FakeTimesource{now} +} + +func (f FakeTimesource) Start(ctx context.Context) error { + return nil +} + +func (f FakeTimesource) Stop() {} + +func (f FakeTimesource) Now() time.Time { + return f.now +} + +type Timesource interface { + Now() time.Time +} + func TestMsgHash(t *testing.T) { privKeyB, _ := hex.DecodeString("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6") prvKey, _ := crypto.ToECDSA(privKeyB) @@ -23,31 +44,52 @@ func TestMsgHash(t *testing.T) { payload, _ := hex.DecodeString("1A12E077D0E89F9CAC11FBBB6A676C86120B5AD3E248B1F180E98F15EE43D2DFCF62F00C92737B2FF6F59B3ABA02773314B991C41DC19ADB0AD8C17C8E26757B") contentTopic := "content-topic" pubsubTopic := "pubsub-topic" + ephemeral := true + timestamp := time.Unix(0, 1683208172339052800) msg := &pb.WakuMessage{ Payload: payload, ContentTopic: contentTopic, - Timestamp: utils.GetUnixEpoch(timesource.NewDefaultClock()), + Timestamp: timestamp.UnixNano(), + Ephemeral: ephemeral, } err := SignMessage(prvKey, pubsubTopic, msg) require.NoError(t, err) - expectedSignature, _ := hex.DecodeString("B139487797A242291E0DD3F689777E559FB749D565D55FF202C18E24F21312A555043437B4F808BB0D21D542D703873DC712D76A3BAF1C5C8FF754210D894AD4") + expectedSignature, _ := hex.DecodeString("127FA211B2514F0E974A055392946DC1A14052182A6ABEFB8A6CD7C51DA1BF2E40595D28EF1A9488797C297EED3AAC45430005FB3A7F037BDD9FC4BD99F59E63") require.True(t, bytes.Equal(expectedSignature, msg.Meta)) msgData, _ := proto.Marshal(msg) - expectedMessageHash, _ := hex.DecodeString("0914369D6D0C13783A8E86409FE42C68D8E8296456B9A9468C845006BCE5B9B2") + expectedMessageHash, _ := hex.DecodeString("662F8C20A335F170BD60ABC1F02AD66F0C6A6EE285DA2A53C95259E7937C0AE9") messageHash := MsgHash(pubsubTopic, msg) require.True(t, bytes.Equal(expectedMessageHash, messageHash)) - myValidator := validatorFnBuilder(pubsubTopic, &prvKey.PublicKey) - + myValidator := validatorFnBuilder(NewFakeTimesource(timestamp), pubsubTopic, &prvKey.PublicKey) result := myValidator(context.Background(), "", &pubsub.Message{ Message: &pubsub_pb.Message{ Data: msgData, }, }) require.True(t, result) + + // Exceed 5m window in both directions + now5m1sInPast := timestamp.Add(-5 * time.Minute).Add(-1 * time.Minute) + myValidator = validatorFnBuilder(NewFakeTimesource(now5m1sInPast), pubsubTopic, &prvKey.PublicKey) + result = myValidator(context.Background(), "", &pubsub.Message{ + Message: &pubsub_pb.Message{ + Data: msgData, + }, + }) + require.False(t, result) + + now5m1sInFuture := timestamp.Add(5 * time.Minute).Add(1 * time.Minute) + myValidator = validatorFnBuilder(NewFakeTimesource(now5m1sInFuture), pubsubTopic, &prvKey.PublicKey) + result = myValidator(context.Background(), "", &pubsub.Message{ + Message: &pubsub_pb.Message{ + Data: msgData, + }, + }) + require.False(t, result) }