From 837a0f2708f7bfb6c8f9fb2de6887cca876ca458 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Tue, 2 May 2023 11:10:45 -0400 Subject: [PATCH] chore: add unit test for signed validator and --protected-topic-flag --- cmd/waku/flags.go | 8 ++++ cmd/waku/main.go | 1 + waku/cliutils/protected_topic.go | 55 +++++++++++++++++++++++ waku/node.go | 5 +++ waku/options.go | 2 + waku/v2/protocol/relay/validators.go | 16 ++++--- waku/v2/protocol/relay/validators_test.go | 47 +++++++++++++++++++ 7 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 waku/cliutils/protected_topic.go create mode 100644 waku/v2/protocol/relay/validators_test.go diff --git a/cmd/waku/flags.go b/cmd/waku/flags.go index f9cf6f81..05418c66 100644 --- a/cmd/waku/flags.go +++ b/cmd/waku/flags.go @@ -206,6 +206,14 @@ var ( Destination: &options.Relay.Topics, EnvVars: []string{"WAKUNODE2_TOPICS"}, }) + ProtectedTopics = cliutils.NewGenericFlagMultiValue(&cli.GenericFlag{ + Name: "protected-topic", + Usage: "Topics and its public key to be used for message validation, topic:pubkey. Argument may be repeated.", + EnvVars: []string{"WAKUNODE2_PROTECTED_TOPIC"}, + Value: &cliutils.ProtectedTopicSlice{ + Values: &options.Relay.ProtectedTopics, + }, + }) RelayPeerExchange = altsrc.NewBoolFlag(&cli.BoolFlag{ Name: "relay-peer-exchange", Value: false, diff --git a/cmd/waku/main.go b/cmd/waku/main.go index b2feb63b..e1713c9c 100644 --- a/cmd/waku/main.go +++ b/cmd/waku/main.go @@ -45,6 +45,7 @@ func main() { AgentString, Relay, Topics, + ProtectedTopics, RelayPeerExchange, MinRelayPeersToPublish, StoreNodeFlag, diff --git a/waku/cliutils/protected_topic.go b/waku/cliutils/protected_topic.go new file mode 100644 index 00000000..e3302690 --- /dev/null +++ b/waku/cliutils/protected_topic.go @@ -0,0 +1,55 @@ +package cliutils + +import ( + "crypto/ecdsa" + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type ProtectedTopic struct { + Topic string + PublicKey *ecdsa.PublicKey +} + +func (p ProtectedTopic) String() string { + pubKBytes := crypto.FromECDSAPub(p.PublicKey) + return fmt.Sprintf("%s:%s", p.Topic, hex.EncodeToString(pubKBytes)) +} + +type ProtectedTopicSlice struct { + Values *[]ProtectedTopic +} + +func (k *ProtectedTopicSlice) Set(value string) error { + protectedTopicParts := strings.Split(value, ":") + if len(protectedTopicParts) != 2 { + return errors.New("expected topic_name:hex_encoded_public_key") + } + + pubk, err := crypto.UnmarshalPubkey(common.FromHex(protectedTopicParts[1])) + if err != nil { + return err + } + *k.Values = append(*k.Values, ProtectedTopic{ + Topic: protectedTopicParts[0], + PublicKey: pubk, + }) + return nil +} + +func (v *ProtectedTopicSlice) String() string { + if v.Values == nil { + return "" + } + var output []string + for _, v := range *v.Values { + output = append(output, v.String()) + } + + return strings.Join(output, ", ") +} diff --git a/waku/node.go b/waku/node.go index 18b44c11..cc49a4ce 100644 --- a/waku/node.go +++ b/waku/node.go @@ -300,6 +300,11 @@ func Execute(options Options) { failOnErr(err, "Error subscring to topic") wakuNode.Broadcaster().Unregister(&nodeTopic, sub.C) } + + for _, protectedTopic := range options.Relay.ProtectedTopics { + err := wakuNode.Relay().AddSignedTopicValidator(protectedTopic.Topic, protectedTopic.PublicKey) + failOnErr(err, "Error adding signed topic validator") + } } for _, n := range options.StaticNodes { diff --git a/waku/options.go b/waku/options.go index d9d51c2e..b9c798a1 100644 --- a/waku/options.go +++ b/waku/options.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" + "github.com/waku-org/go-waku/waku/cliutils" ) // DiscV5Options are settings to enable a modified version of Ethereum’s Node @@ -24,6 +25,7 @@ type DiscV5Options struct { type RelayOptions struct { Enable bool Topics cli.StringSlice + ProtectedTopics []cliutils.ProtectedTopic PeerExchange bool MinRelayPeersToPublish int } diff --git a/waku/v2/protocol/relay/validators.go b/waku/v2/protocol/relay/validators.go index 97ceb444..1475ac2f 100644 --- a/waku/v2/protocol/relay/validators.go +++ b/waku/v2/protocol/relay/validators.go @@ -21,9 +21,10 @@ func MsgHash(pubSubTopic string, msg *pb.WakuMessage) []byte { return hash.SHA256([]byte(pubSubTopic), msg.Payload, []byte(msg.ContentTopic)) } -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, func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { +type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool + +func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn { + return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { msg := new(pb.WakuMessage) err := proto.Unmarshal(message.Data, msg) if err != nil { @@ -34,11 +35,16 @@ func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.Publi signature := msg.Meta return ecdsa.VerifyASN1(publicKey, msgHash, signature) - }) + } +} + +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)) return err } -func (w *WakuRelay) SignMessage(privKey *ecdsa.PrivateKey, topic string, msg *pb.WakuMessage) error { +func SignMessage(privKey *ecdsa.PrivateKey, topic string, msg *pb.WakuMessage) error { msgHash := MsgHash(topic, msg) sign, err := ecdsa.SignASN1(rand.Reader, privKey, msgHash) if err != nil { diff --git a/waku/v2/protocol/relay/validators_test.go b/waku/v2/protocol/relay/validators_test.go new file mode 100644 index 00000000..555a4aa0 --- /dev/null +++ b/waku/v2/protocol/relay/validators_test.go @@ -0,0 +1,47 @@ +package relay + +import ( + "bytes" + "context" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "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" + "google.golang.org/protobuf/proto" +) + +func TestMsgHash(t *testing.T) { + privKeyB, _ := hexutil.Decode("0x5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6") + prvKey, _ := crypto.ToECDSA(privKeyB) + + payload, _ := hexutil.Decode("0x3af5c7a8d71498e82e1991089d8429448f3b78277fac141af9052e77fc003dfb") + contentTopic := "my-content-topic" + pubsubTopic := "some-spam-protected-topic" + + msg := &pb.WakuMessage{ + Payload: payload, + ContentTopic: contentTopic, + } + + err := SignMessage(prvKey, pubsubTopic, msg) + require.NoError(t, err) + + msgData, _ := proto.Marshal(msg) + + expectedMessageHash, _ := hexutil.Decode("0xd0e3231ec48f9c0cf9306b7100c30b4e85c78854b67b41e4ee388fb4610f543d") + messageHash := MsgHash(pubsubTopic, msg) + require.True(t, bytes.Equal(expectedMessageHash, messageHash)) + + myValidator := validatorFnBuilder(pubsubTopic, &prvKey.PublicKey) + + result := myValidator(context.Background(), "", &pubsub.Message{ + Message: &pubsub_pb.Message{ + Data: msgData, + }, + }) + require.True(t, result) +}