2023-04-05 17:03:29 -04:00
package relay
import (
"context"
"crypto/ecdsa"
2023-05-04 11:56:56 -04:00
"encoding/binary"
2023-05-22 17:03:40 -04:00
"encoding/hex"
2023-05-04 11:56:56 -04:00
"time"
2023-04-05 17:03:29 -04:00
2023-05-03 11:59:47 -04:00
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
2023-04-05 17:03:29 -04:00
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
2023-09-07 17:39:10 -04:00
2023-04-05 17:03:29 -04:00
"github.com/waku-org/go-waku/waku/v2/hash"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
2023-05-04 11:56:56 -04:00
"github.com/waku-org/go-waku/waku/v2/timesource"
2023-04-05 17:03:29 -04:00
"go.uber.org/zap"
)
2023-07-19 12:25:35 -04:00
func msgHash ( pubSubTopic string , msg * pb . WakuMessage ) [ ] byte {
2023-05-04 11:56:56 -04:00
timestampBytes := make ( [ ] byte , 8 )
2023-11-07 15:48:43 -04:00
binary . LittleEndian . PutUint64 ( timestampBytes , uint64 ( msg . GetTimestamp ( ) ) )
2023-05-04 11:56:56 -04:00
var ephemeralByte byte
2023-11-07 15:48:43 -04:00
if msg . GetEphemeral ( ) {
2023-05-04 11:56:56 -04:00
ephemeralByte = 1
}
return hash . SHA256 (
[ ] byte ( pubSubTopic ) ,
msg . Payload ,
[ ] byte ( msg . ContentTopic ) ,
timestampBytes ,
[ ] byte { ephemeralByte } ,
)
}
2023-09-07 17:39:10 -04:00
type validatorFn = func ( ctx context . Context , msg * pb . WakuMessage , topic string ) bool
2023-05-04 11:56:56 -04:00
2023-09-07 17:39:10 -04:00
func ( w * WakuRelay ) RegisterDefaultValidator ( fn validatorFn ) {
w . topicValidatorMutex . Lock ( )
defer w . topicValidatorMutex . Unlock ( )
w . defaultTopicValidators = append ( w . defaultTopicValidators , fn )
}
2023-05-04 11:56:56 -04:00
2023-09-07 17:39:10 -04:00
func ( w * WakuRelay ) RegisterTopicValidator ( topic string , fn validatorFn ) {
w . topicValidatorMutex . Lock ( )
defer w . topicValidatorMutex . Unlock ( )
2023-05-04 11:56:56 -04:00
2023-09-07 17:39:10 -04:00
w . topicValidators [ topic ] = append ( w . topicValidators [ topic ] , fn )
2023-04-05 17:03:29 -04:00
}
2023-09-07 17:39:10 -04:00
func ( w * WakuRelay ) RemoveTopicValidator ( topic string ) {
w . topicValidatorMutex . Lock ( )
defer w . topicValidatorMutex . Unlock ( )
2023-05-02 11:10:45 -04:00
2023-09-07 17:39:10 -04:00
delete ( w . topicValidators , topic )
}
func ( w * WakuRelay ) topicValidator ( topic string ) func ( ctx context . Context , peerID peer . ID , message * pubsub . Message ) bool {
2023-05-02 11:10:45 -04:00
return func ( ctx context . Context , peerID peer . ID , message * pubsub . Message ) bool {
2023-10-24 12:26:02 -04:00
msg , err := pb . Unmarshal ( message . Data )
2023-04-05 17:03:29 -04:00
if err != nil {
return false
}
2023-09-07 17:39:10 -04:00
w . topicValidatorMutex . RLock ( )
2023-11-10 15:00:12 +04:00
validators := w . topicValidators [ topic ]
2023-09-07 17:39:10 -04:00
validators = append ( validators , w . defaultTopicValidators ... )
w . topicValidatorMutex . RUnlock ( )
2023-11-10 15:00:12 +04:00
exists := len ( validators ) > 0
2023-09-07 17:39:10 -04:00
if exists {
for _ , v := range validators {
if ! v ( ctx , msg , topic ) {
return false
}
}
2023-05-04 11:56:56 -04:00
}
2023-09-07 17:39:10 -04:00
return true
}
2023-05-02 11:10:45 -04:00
}
2023-07-19 12:25:35 -04:00
// AddSignedTopicValidator registers a gossipsub validator for a topic which will check that messages Meta field contains a valid ECDSA signature for the specified pubsub topic. This is used as a DoS prevention mechanism
2023-05-22 17:03:40 -04:00
func ( w * WakuRelay ) AddSignedTopicValidator ( topic string , publicKey * ecdsa . PublicKey ) error {
2024-06-04 18:46:28 -04:00
w . log . Info ( "adding validator to signed topic" , zap . String ( "topic" , topic ) , zap . String ( "publicKey" , hex . EncodeToString ( secp256k1 . S256 ( ) . Marshal ( publicKey . X , publicKey . Y ) ) ) )
2023-05-09 10:48:55 -04:00
2023-09-07 17:39:10 -04:00
fn := signedTopicBuilder ( w . timesource , publicKey )
2023-05-09 10:48:55 -04:00
2023-09-07 17:39:10 -04:00
w . RegisterTopicValidator ( topic , fn )
2023-05-04 10:04:54 -04:00
if ! w . IsSubscribed ( topic ) {
w . log . Warn ( "relay is not subscribed to signed topic" , zap . String ( "topic" , topic ) )
}
return nil
2023-04-05 17:03:29 -04:00
}
2023-09-07 17:39:10 -04:00
const messageWindowDuration = time . Minute * 5
func withinTimeWindow ( t timesource . Timesource , msg * pb . WakuMessage ) bool {
2023-11-07 15:48:43 -04:00
if msg . GetTimestamp ( ) == 0 {
2023-09-07 17:39:10 -04:00
return false
}
now := t . Now ( )
2023-11-07 15:48:43 -04:00
msgTime := time . Unix ( 0 , msg . GetTimestamp ( ) )
2023-09-07 17:39:10 -04:00
return now . Sub ( msgTime ) . Abs ( ) <= messageWindowDuration
}
func signedTopicBuilder ( t timesource . Timesource , publicKey * ecdsa . PublicKey ) validatorFn {
publicKeyBytes := crypto . FromECDSAPub ( publicKey )
return func ( ctx context . Context , msg * pb . WakuMessage , topic string ) bool {
if ! withinTimeWindow ( t , msg ) {
return false
}
msgHash := msgHash ( topic , msg )
signature := msg . Meta
return secp256k1 . VerifySignature ( publicKeyBytes , msgHash , signature )
}
}
2023-07-19 12:25:35 -04:00
// SignMessage adds an ECDSA signature to a WakuMessage as an opt-in mechanism for DoS prevention
2023-05-26 10:42:25 -04:00
func SignMessage ( privKey * ecdsa . PrivateKey , msg * pb . WakuMessage , pubsubTopic string ) error {
2023-07-19 12:25:35 -04:00
msgHash := msgHash ( pubsubTopic , msg )
2023-05-03 11:59:47 -04:00
sign , err := secp256k1 . Sign ( msgHash , crypto . FromECDSA ( privKey ) )
2023-04-05 17:03:29 -04:00
if err != nil {
return err
}
2023-05-26 11:18:00 -04:00
msg . Meta = sign [ 0 : 64 ] // Remove V
2023-04-05 17:03:29 -04:00
return nil
}