2024-07-20 01:09:42 +00:00
|
|
|
package events
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-07-22 17:50:43 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
2024-07-20 01:09:42 +00:00
|
|
|
"fmt"
|
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
2024-07-20 01:09:42 +00:00
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
2024-07-22 17:50:43 +00:00
|
|
|
"golang.org/x/net/websocket"
|
2024-07-20 01:09:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Event struct {
|
2024-07-22 17:50:43 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
PubKey string `json:"pubkey"`
|
|
|
|
CreatedAt int64 `json:"created_at"`
|
|
|
|
Kind int `json:"kind"`
|
|
|
|
Tags [][]string `json:"tags"`
|
|
|
|
Content string `json:"content"`
|
|
|
|
Sig string `json:"sig"`
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
var collections = make(map[int]*mongo.Collection)
|
2024-07-20 01:09:42 +00:00
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
func InitCollections(client *mongo.Client, kinds ...int) {
|
|
|
|
for _, kind := range kinds {
|
|
|
|
collectionName := fmt.Sprintf("event-kind%d", kind)
|
|
|
|
collections[kind] = client.Database("grain").Collection(collectionName)
|
|
|
|
indexModel := mongo.IndexModel{
|
|
|
|
Keys: bson.D{{Key: "id", Value: 1}},
|
|
|
|
Options: options.Index().SetUnique(true),
|
|
|
|
}
|
|
|
|
_, err := collections[kind].Indexes().CreateOne(context.TODO(), indexModel)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to create index on %s: %v\n", collectionName, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-20 01:09:42 +00:00
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
func GetCollection(kind int, client *mongo.Client) *mongo.Collection {
|
|
|
|
if collection, exists := collections[kind]; exists {
|
|
|
|
return collection
|
|
|
|
}
|
|
|
|
collectionName := fmt.Sprintf("event-kind%d", kind)
|
|
|
|
collection := client.Database("grain").Collection(collectionName)
|
|
|
|
collections[kind] = collection
|
2024-07-20 01:09:42 +00:00
|
|
|
indexModel := mongo.IndexModel{
|
|
|
|
Keys: bson.D{{Key: "id", Value: 1}},
|
|
|
|
Options: options.Index().SetUnique(true),
|
|
|
|
}
|
2024-07-22 17:50:43 +00:00
|
|
|
_, err := collection.Indexes().CreateOne(context.TODO(), indexModel)
|
2024-07-20 01:09:42 +00:00
|
|
|
if err != nil {
|
2024-07-22 17:50:43 +00:00
|
|
|
fmt.Printf("Failed to create index on %s: %v\n", collectionName, err)
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
2024-07-22 17:50:43 +00:00
|
|
|
return collection
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
func HandleEvent(ctx context.Context, evt Event, client *mongo.Client, ws *websocket.Conn) {
|
|
|
|
if !ValidateEvent(evt) {
|
|
|
|
sendOKResponse(ws, evt.ID, false, "invalid: signature verification failed")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
collection := GetCollection(evt.Kind, client)
|
|
|
|
|
|
|
|
var err error
|
2024-07-20 01:09:42 +00:00
|
|
|
switch evt.Kind {
|
|
|
|
case 0:
|
2024-07-22 17:50:43 +00:00
|
|
|
err = HandleEventKind0(ctx, evt, collection)
|
2024-07-20 01:09:42 +00:00
|
|
|
case 1:
|
2024-07-22 17:50:43 +00:00
|
|
|
err = HandleEventKind1(ctx, evt, collection)
|
2024-07-20 01:09:42 +00:00
|
|
|
default:
|
2024-07-22 17:50:43 +00:00
|
|
|
err = HandleDefaultEvent(ctx, evt, collection)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
sendOKResponse(ws, evt.ID, false, fmt.Sprintf("error: %v", err))
|
|
|
|
return
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
2024-07-22 17:50:43 +00:00
|
|
|
|
|
|
|
sendOKResponse(ws, evt.ID, true, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func sendOKResponse(ws *websocket.Conn, eventID string, status bool, message string) {
|
|
|
|
response := []interface{}{"OK", eventID, status, message}
|
|
|
|
responseBytes, _ := json.Marshal(response)
|
|
|
|
websocket.Message.Send(ws, string(responseBytes))
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 17:50:43 +00:00
|
|
|
func ValidateEvent(evt Event) bool {
|
|
|
|
serializedEvent := SerializeEvent(evt)
|
|
|
|
hash := sha256.Sum256(serializedEvent)
|
|
|
|
eventID := hex.EncodeToString(hash[:])
|
|
|
|
if eventID != evt.ID {
|
|
|
|
return false
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|
2024-07-22 17:50:43 +00:00
|
|
|
|
|
|
|
sigBytes, err := hex.DecodeString(evt.Sig)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
sig, err := schnorr.ParseSignature(sigBytes)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
pubKeyBytes, err := hex.DecodeString(evt.PubKey)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return sig.Verify(hash[:], pubKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
func SerializeEvent(evt Event) []byte {
|
|
|
|
eventData := []interface{}{
|
|
|
|
0,
|
|
|
|
evt.PubKey,
|
|
|
|
evt.CreatedAt,
|
|
|
|
evt.Kind,
|
|
|
|
evt.Tags,
|
|
|
|
evt.Content,
|
|
|
|
}
|
|
|
|
serializedEvent, _ := json.Marshal(eventData)
|
|
|
|
return serializedEvent
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleDefaultEvent(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
|
|
|
_, err := collection.InsertOne(ctx, evt)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error inserting default event into MongoDB: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println("Inserted default event into MongoDB:", evt.ID)
|
|
|
|
return nil
|
2024-07-20 01:09:42 +00:00
|
|
|
}
|